Skip to content

[Role] Feature: Add az role deny-assignment create/delete commands#33109

Open
jruttle wants to merge 13 commits into
Azure:devfrom
jruttle:jruttle/add-deny-assignment-commands
Open

[Role] Feature: Add az role deny-assignment create/delete commands#33109
jruttle wants to merge 13 commits into
Azure:devfrom
jruttle:jruttle/add-deny-assignment-commands

Conversation

@jruttle
Copy link
Copy Markdown
Member

@jruttle jruttle commented Mar 31, 2026

DescriptionAdds az role deny-assignment create and az role deny-assignment delete commands for managing user-assigned deny assignments, matching the existing az role assignment pattern.This implements the PUT/DELETE operations added in the Microsoft.Authorization/denyAssignments API (2024-07-01-preview), as specified in TypeSpec PR #41617.### Two Assignment ModesThe create command supports two modes for targeting principals:Everyone mode (default): Denies all principals at the scope. At least one excluded principal is required via --exclude-principal-ids:az role deny-assignment create --name "DenyAll" --scope /subscriptions/{sub} \ --actions "Microsoft.Compute/virtualMachines/delete" \ --exclude-principal-ids {your-object-id}Per-principal mode: Denies a specific User or ServicePrincipal via --principal-id and --principal-type:az role deny-assignment create --name "DenyUser" --scope /subscriptions/{sub} \ --actions "Microsoft.Compute/virtualMachines/delete" \ --principal-id {user-object-id} --principal-type User### Service ConstraintsUser-assigned deny assignments have specific restrictions enforced by the service:- Group principals are not permitted — only User or ServicePrincipal- No DataActions — only Actions/NotActions are supported- No Read actions — actions like */read are not permitted- No DoNotApplyToChildScopes — this property is not supported- Single principal per UADA — one principal per deny assignment (enforced by backend)### Commands- az role deny-assignment list — List deny assignments (existing, enhanced)- az role deny-assignment show — Show a deny assignment (existing, enhanced)- az role deny-assignment create — Create a user-assigned deny assignment- az role deny-assignment delete — Delete a user-assigned deny assignment### Files Changed- commands.py — Command registration for role deny-assignment group- custom.py — Business logic with dual-mode principal handling and validation- _params.py — Parameter definitions including --principal-id and --principal-type- _help.py — Help text with examples for both Everyone and per-principal modes- linter_exclusions.yml — Exclusions for long parameter names- tests/latest/test_deny_assignment.py — Unit tests (list, show, CRUD, per-principal, Group rejection, param validation)### Dependency> Note: The create and delete operations depend on the Python SDK PR azure-sdk-for-python#46223 being merged first. This PR pins azure-mgmt-authorization to 5.0.0b2 (released 7 May 2026 on PyPI), which includes the create_or_update and delete methods on DenyAssignmentsOperations.### TestingTests are included in test_deny_assignment.py. Full end-to-end testing requires:1. A subscription with the Microsoft.Authorization/SubscriptionAllowedToOperateUserAssignedDenyAssignment feature flag registered2. The updated Python SDK with create/delete support### Related- TypeSpec PR: azure-rest-api-specs#41617 (merged)- Python SDK auto-gen PR: azure-sdk-for-python#46223 (awaiting review)- Go SDK auto-gen PR: azure-sdk-for-go#26544 (awaiting review)- Java SDK auto-gen PR: azure-sdk-for-java#48751 (awaiting review)- JS SDK auto-gen PR: azure-sdk-for-js#38079 (awaiting review)- PowerShell PR: azure-powershell#29340 (merged ✅)

Copilot AI review requested due to automatic review settings March 31, 2026 13:10
@azure-client-tools-bot-prd
Copy link
Copy Markdown

azure-client-tools-bot-prd Bot commented Mar 31, 2026

❌AzureCLI-FullTest
🔄acr
🔄latest
🔄3.12
🔄3.13
🔄acs
🔄latest
🔄3.12
🔄3.13
🔄advisor
🔄latest
🔄3.12
️✔️3.13
❌ams
❌latest
❌3.12
Type Test Case Error Message Line
Failed test_ams_sp_create_reset self = <azure.cli.testsdk.base.ExecutionResult object at 0x7f824e1ee060>
cli_ctx = <azure.cli.core.mock.DummyCli object at 0x7f824ffbf980>
command = 'ams account sp create -a ams000003 -n ams000003-access-sp -g clitest.rg000001 -p spp1!000004 --role Owner'
expect_failure = False

    def in_process_execute(self, cli_ctx, command, expect_failure=False):
        from io import StringIO
        from vcr.errors import CannotOverwriteExistingCassetteException
    
        if command.startswith('az '):
            command = command[3:]
    
        stdout_buf = StringIO()
        logging_buf = StringIO()
        try:
            # issue: stderr cannot be redirect in this form, as a result some failure information
            # is lost when command fails.
>           self.exit_code = cli_ctx.invoke(shlex.split(command), out_file=stdout_buf) or 0
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

src/azure-cli-testsdk/azure/cli/testsdk/base.py:303: 
                                        
env/lib/python3.12/site-packages/knack/cli.py:245: in invoke
    exit_code = self.exception_handler(ex)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/init.py:157: in exception_handler
    return handle_exception(ex)
           ^^^^^^^^^^^^^^^^^^^^
src/azure-cli-testsdk/azure/cli/testsdk/patches.py:33: in handle_main_exception
    raise ex
env/lib/python3.12/site-packages/knack/cli.py:233: in invoke
    cmd_result = self.invocation.execute(args)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:677: in execute
    raise ex
src/azure-cli-core/azure/cli/core/commands/init.py:820: in run_jobs_serially
    results.append(self.run_job(expanded_arg, cmd_copy))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:812: in run_job
    return cmd_copy.exception_handler(ex)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli/azure/cli/command_modules/ams/exception_handler.py:16: in ams_exception_handler
    raise ex
src/azure-cli-core/azure/cli/core/commands/init.py:789: in run_job
    result = cmd_copy(params)
             ^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:335: in call
    return self.handler(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/command_operation.py:120: in handler
    return op(**command_args)
           ^^^^^^^^^^^^^^^^^^
src/azure-cli/azure/cli/command_modules/ams/operations/sp.py:94: in create_or_update_assign_sp_to_mediaservice
    assign_role(cmd, entity_name_string="role assignment", role=role, sp_oid=sp_oid, scope=ams.id)
src/azure-cli/azure/cli/command_modules/ams/operations/sp.py:245: in inner
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
src/azure-cli/azure/cli/command_modules/ams/operations/sp.py:302: in assign_role
    assignments = list_role_assignments(cmd, sp_oid, scope)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli/azure/cli/command_modules/ams/operations/sp.py:150: in list_role_assignments
    assignments = search_role_assignments(assignments_client, assignee_object_id)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli/azure/cli/command_modules/ams/operations/sp.py:296: in search_role_assignments
    assignments = list(assignments_client.list_for_subscription(filter=f))
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/paging.py:136: in next
    return next(self.page_iterator)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/paging.py:82: in next
    self.response = self.get_next(self.continuation_token)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/mgmt/authorization/operations/operations.py:10304: in get_next
    pipeline_response: PipelineResponse = self.client.pipeline.run(  # pylint: disable=protected-access
env/lib/python3.12/site-packages/azure/core/pipeline/base.py:242: in run
    return first_node.send(pipeline_request)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/mgmt/core/policies/base.py:95: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/policies/redirect.py:205: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/policies/retry.py:545: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/policies/authentication.py:194: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/base.py:130: in send
    self.sender.send(request.http_request, **request.context.options),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/transport/requests_basic.py:375: in send
    response = self.session.request(  # type: ignore
env/lib/python3.12/site-packages/requests/sessions.py:592: in request
    resp = self.send(prep, **send_kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/requests/sessions.py:706: in send
    r = adapter.send(request, **kwargs)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/requests/adapters.py:645: in send
    resp = conn.urlopen(
env/lib/python3.12/site-packages/urllib3/connectionpool.py:787: in urlopen
    response = self.make_request(
env/lib/python3.12/site-packages/urllib3/connectionpool.py:534: in make_request
    response = conn.getresponse()
               ^^^^^^^^^^^^^^^^^^
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
    

self = <VCRRequestsHTTPSConnection/mnt/vss/work/1/s/src/azure-cli/azure/cli/command_modules/ams/tests/latest/recordings/test_ams_sp_create_reset.yaml(host='management.azure.com', port=443) at 0x7f824dc747a0>
 = False, kwargs = {}

    def getresponse(self, 
=False, **kwargs):
        """Retrieve the response"""
        # Check to see if the cassette has a response for this request. If so,
        # then return it
        if self.cassette.can_play_response_for(self.vcr_request):
            log.info(f"Playing response for {self.vcr_request} from cassette")
            response = self.cassette.play_response(self.vcr_request)
            return VCRHTTPResponse(response)
        else:
            if self.cassette.write_protected and self.cassette.filter_request(self.vcr_request):
>               raise CannotOverwriteExistingCassetteException(
                    cassette=self.cassette,
                    failed_request=self.vcr_request,
                )
E               vcr.errors.CannotOverwriteExistingCassetteException: Can't overwrite existing cassette ('/mnt/vss/work/1/s/src/azure-cli/azure/cli/command_modules/ams/tests/latest/recordings/test_ams_sp_create_reset.yaml') in your current record mode ('once').
E               No match for the request (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01&$filter=principalId%20eq%20%2727e628bb-89e7-44f6-9193-9c3d4955dcff%27>)&nbsp;was&nbsp;found.
E               Found 2 similar requests with 0 different matcher(s) :
E               
E               1 - (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01&$filter=principalId%20eq%20'27e628bb-89e7-44f6-9193-9c3d4955dcff'>).
E               Matchers succeeded : ['method', 'scheme', 'host', 'port', 'path', 'custom_request_query_matcher']
E               Matchers failed :
E               
E               2 - (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01&$filter=principalId%20eq%20'27e628bb-89e7-44f6-9193-9c3d4955dcff'>).
E               Matchers succeeded : ['method', 'scheme', 'host', 'port', 'path', 'custom_request_query_matcher']
E               Matchers failed :

env/lib/python3.12/site-packages/vcr/stubs/init.py:277: CannotOverwriteExistingCassetteException

During handling of the above exception, another exception occurred:

self = <azure.cli.command_modules.ams.tests.latest.test_ams_sp_scenarios.AmsSpTests testMethod=test_ams_sp_create_reset>
resource_group = 'clitest.rg000001'
storage_account_for_create = 'clitest000002'

    @ResourceGroupPreparer()
    @StorageAccountPreparer(parameter_name='storage_account_for_create')
    @AllowLargeResponse()
    def test_ams_sp_create_reset(self, resource_group, storage_account_for_create):
        with mock.patch('azure.cli.command_modules.ams.operations.sp.gen_guid', side_effect=self.create_guid):
            amsname = self.create_random_name(prefix='ams', length=12)
    
            self.kwargs.update({
                'amsname': amsname,
                'storageAccount': storage_account_for_create,
                'location': 'westus2'
            })
    
            self.cmd('az ams account create -n {amsname} -g {rg} --storage-account {storageAccount} -l {location}', checks=[
                self.check('name', '{amsname}'),
                self.check('location', 'West US 2')
            ])
    
            spPassword = self.create_random_name(prefix='spp1!', length=16)
            spNewPassword = self.create_random_name(prefix='spp1!', length=16)
    
            self.kwargs.update({
                'spName': '{}-access-sp'.format(amsname),
                'spPassword': spPassword,
                'spNewPassword': spNewPassword,
                'role': 'Owner'
            })
    
            try:
>               spjson = self.cmd('az ams account sp create -a {amsname} -n {spName} -g {rg} -p {spPassword} --role {role}', checks=[
                    self.check('AadSecretFriendlyName', '{spPassword}'),
                    self.check('ResourceGroup', '{rg}'),
                    self.check('AccountName', '{amsname}'),
                    self.check('Role', '{role}')
                ]).get_output_in_json()

src/azure-cli/azure/cli/command_modules/ams/tests/latest/test_ams_sp_scenarios.py:42: 
 
 
 
 
 
 
 
 
                                
src/azure-cli-testsdk/azure/cli/testsdk/base.py:177: in cmd
    return execute(self.cli_ctx, command, expect_failure=expect_failure).assert_with_checks(checks)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-testsdk/azure/cli/testsdk/base.py:252: in init
    self.in_process_execute(cli_ctx, command, expect_failure=expect_failure)
 
                                       

self = <azure.cli.testsdk.base.ExecutionResult object at 0x7f824e1ee060>
cli_ctx = <azure.cli.core.mock.DummyCli object at 0x7f824ffbf980>
command = 'ams account sp create -a ams000003 -n ams000003-access-sp -g clitest.rg000001 -p spp1!000004 --role Owner'
expect_failure = False

    def _in_process_execute(self, cli_ctx, command, expect_failure=False):
        from io import StringIO
        from vcr.errors import CannotOverwriteExistingCassetteException
    
        if command.startswith('az '):
            command = command[3:]
    
        stdout_buf = StringIO()
        logging_buf = StringIO()
        try:
            # issue: stderr cannot be redirect in this form, as a result some failure information
            # is lost when command fails.
            self.exit_code = cli_ctx.invoke(shlex.split(command), out_file=stdout_buf) or 0
            self.output = stdout_buf.getvalue()
            self.applog = logging_buf.getvalue()
    
        except CannotOverwriteExistingCassetteException as ex:
>           raise AssertionError(ex)
E           AssertionError: Can't overwrite existing cassette ('/mnt/vss/_work/1/s/src/azure-cli/azure/cli/command_modules/ams/tests/latest/recordings/test_ams_sp_create_reset.yaml') in your current record mode ('once').
E           No match for the request (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01&$filter=principalId%20eq%20%2727e628bb-89e7-44f6-9193-9c3d4955dcff%27>)&nbsp;was&nbsp;found.
E           Found 2 similar requests with 0 different matcher(s) :
E           
E           1 - (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01&$filter=principalId%20eq%20'27e628bb-89e7-44f6-9193-9c3d4955dcff'>).
E           Matchers succeeded : ['method', 'scheme', 'host', 'port', 'path', '_custom_request_query_matcher']
E           Matchers failed :
E           
E           2 - (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01&$filter=principalId%20eq%20'27e628bb-89e7-44f6-9193-9c3d4955dcff'>).
E           Matchers succeeded : ['method', 'scheme', 'host', 'port', 'path', '_custom_request_query_matcher']
E           Matchers failed :

src/azure-cli-testsdk/azure/cli/testsdk/base.py:308: AssertionError
azure/cli/command_modules/ams/tests/latest/test_ams_sp_scenarios.py:12
❌3.13
Type Test Case Error Message Line
Failed test_ams_sp_create_reset self = <azure.cli.testsdk.base.ExecutionResult object at 0x7f5e51dedbd0>
cli_ctx = <azure.cli.core.mock.DummyCli object at 0x7f5e53a26850>
command = 'ams account sp create -a ams000003 -n ams000003-access-sp -g clitest.rg000001 -p spp1!000004 --role Owner'
expect_failure = False

    def in_process_execute(self, cli_ctx, command, expect_failure=False):
        from io import StringIO
        from vcr.errors import CannotOverwriteExistingCassetteException
    
        if command.startswith('az '):
            command = command[3:]
    
        stdout_buf = StringIO()
        logging_buf = StringIO()
        try:
            # issue: stderr cannot be redirect in this form, as a result some failure information
            # is lost when command fails.
>           self.exit_code = cli_ctx.invoke(shlex.split(command), out_file=stdout_buf) or 0
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

src/azure-cli-testsdk/azure/cli/testsdk/base.py:303: 
                                        
env/lib/python3.13/site-packages/knack/cli.py:245: in invoke
    exit_code = self.exception_handler(ex)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/init.py:157: in exception_handler
    return handle_exception(ex)
           ^^^^^^^^^^^^^^^^^^^^
src/azure-cli-testsdk/azure/cli/testsdk/patches.py:33: in handle_main_exception
    raise ex
env/lib/python3.13/site-packages/knack/cli.py:233: in invoke
    cmd_result = self.invocation.execute(args)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:677: in execute
    raise ex
src/azure-cli-core/azure/cli/core/commands/init.py:820: in run_jobs_serially
    results.append(self.run_job(expanded_arg, cmd_copy))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:812: in run_job
    return cmd_copy.exception_handler(ex)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli/azure/cli/command_modules/ams/exception_handler.py:16: in ams_exception_handler
    raise ex
src/azure-cli-core/azure/cli/core/commands/init.py:789: in run_job
    result = cmd_copy(params)
             ^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:335: in call
    return self.handler(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/command_operation.py:120: in handler
    return op(**command_args)
           ^^^^^^^^^^^^^^^^^^
src/azure-cli/azure/cli/command_modules/ams/operations/sp.py:94: in create_or_update_assign_sp_to_mediaservice
    assign_role(cmd, entity_name_string="role assignment", role=role, sp_oid=sp_oid, scope=ams.id)
src/azure-cli/azure/cli/command_modules/ams/operations/sp.py:245: in inner
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
src/azure-cli/azure/cli/command_modules/ams/operations/sp.py:302: in assign_role
    assignments = list_role_assignments(cmd, sp_oid, scope)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli/azure/cli/command_modules/ams/operations/sp.py:150: in list_role_assignments
    assignments = search_role_assignments(assignments_client, assignee_object_id)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli/azure/cli/command_modules/ams/operations/sp.py:296: in search_role_assignments
    assignments = list(assignments_client.list_for_subscription(filter=f))
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/paging.py:136: in next
    return next(self.page_iterator)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/paging.py:82: in next
    self.response = self.get_next(self.continuation_token)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/mgmt/authorization/operations/operations.py:10304: in get_next
    pipeline_response: PipelineResponse = self.client.pipeline.run(  # pylint: disable=protected-access
env/lib/python3.13/site-packages/azure/core/pipeline/base.py:242: in run
    return first_node.send(pipeline_request)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/mgmt/core/policies/base.py:95: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/policies/redirect.py:205: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/policies/retry.py:545: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/policies/authentication.py:194: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/base.py:130: in send
    self.sender.send(request.http_request, **request.context.options),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/transport/requests_basic.py:375: in send
    response = self.session.request(  # type: ignore
env/lib/python3.13/site-packages/requests/sessions.py:592: in request
    resp = self.send(prep, **send_kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/requests/sessions.py:706: in send
    r = adapter.send(request, **kwargs)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/requests/adapters.py:645: in send
    resp = conn.urlopen(
env/lib/python3.13/site-packages/urllib3/connectionpool.py:787: in urlopen
    response = self.make_request(
env/lib/python3.13/site-packages/urllib3/connectionpool.py:534: in make_request
    response = conn.getresponse()
               ^^^^^^^^^^^^^^^^^^
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
    

self = <VCRRequestsHTTPSConnection/mnt/vss/work/1/s/src/azure-cli/azure/cli/command_modules/ams/tests/latest/recordings/test_ams_sp_create_reset.yaml(host='management.azure.com', port=443) at 0x7f5e51626040>
 = False, kwargs = {}

    def getresponse(self, 
=False, **kwargs):
        """Retrieve the response"""
        # Check to see if the cassette has a response for this request. If so,
        # then return it
        if self.cassette.can_play_response_for(self.vcr_request):
            log.info(f"Playing response for {self.vcr_request} from cassette")
            response = self.cassette.play_response(self.vcr_request)
            return VCRHTTPResponse(response)
        else:
            if self.cassette.write_protected and self.cassette.filter_request(self.vcr_request):
>               raise CannotOverwriteExistingCassetteException(
                    cassette=self.cassette,
                    failed_request=self.vcr_request,
                )
E               vcr.errors.CannotOverwriteExistingCassetteException: Can't overwrite existing cassette ('/mnt/vss/work/1/s/src/azure-cli/azure/cli/command_modules/ams/tests/latest/recordings/test_ams_sp_create_reset.yaml') in your current record mode ('once').
E               No match for the request (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01&$filter=principalId%20eq%20%2727e628bb-89e7-44f6-9193-9c3d4955dcff%27>)&nbsp;was&nbsp;found.
E               Found 2 similar requests with 0 different matcher(s) :
E               
E               1 - (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01&$filter=principalId%20eq%20'27e628bb-89e7-44f6-9193-9c3d4955dcff'>).
E               Matchers succeeded : ['method', 'scheme', 'host', 'port', 'path', 'custom_request_query_matcher']
E               Matchers failed :
E               
E               2 - (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01&$filter=principalId%20eq%20'27e628bb-89e7-44f6-9193-9c3d4955dcff'>).
E               Matchers succeeded : ['method', 'scheme', 'host', 'port', 'path', 'custom_request_query_matcher']
E               Matchers failed :

env/lib/python3.13/site-packages/vcr/stubs/init.py:277: CannotOverwriteExistingCassetteException

During handling of the above exception, another exception occurred:

self = <azure.cli.command_modules.ams.tests.latest.test_ams_sp_scenarios.AmsSpTests testMethod=test_ams_sp_create_reset>
resource_group = 'clitest.rg000001'
storage_account_for_create = 'clitest000002'

    @ResourceGroupPreparer()
    @StorageAccountPreparer(parameter_name='storage_account_for_create')
    @AllowLargeResponse()
    def test_ams_sp_create_reset(self, resource_group, storage_account_for_create):
        with mock.patch('azure.cli.command_modules.ams.operations.sp.gen_guid', side_effect=self.create_guid):
            amsname = self.create_random_name(prefix='ams', length=12)
    
            self.kwargs.update({
                'amsname': amsname,
                'storageAccount': storage_account_for_create,
                'location': 'westus2'
            })
    
            self.cmd('az ams account create -n {amsname} -g {rg} --storage-account {storageAccount} -l {location}', checks=[
                self.check('name', '{amsname}'),
                self.check('location', 'West US 2')
            ])
    
            spPassword = self.create_random_name(prefix='spp1!', length=16)
            spNewPassword = self.create_random_name(prefix='spp1!', length=16)
    
            self.kwargs.update({
                'spName': '{}-access-sp'.format(amsname),
                'spPassword': spPassword,
                'spNewPassword': spNewPassword,
                'role': 'Owner'
            })
    
            try:
>               spjson = self.cmd('az ams account sp create -a {amsname} -n {spName} -g {rg} -p {spPassword} --role {role}', checks=[
                    self.check('AadSecretFriendlyName', '{spPassword}'),
                    self.check('ResourceGroup', '{rg}'),
                    self.check('AccountName', '{amsname}'),
                    self.check('Role', '{role}')
                ]).get_output_in_json()

src/azure-cli/azure/cli/command_modules/ams/tests/latest/test_ams_sp_scenarios.py:42: 
 
 
 
 
 
 
 
 
                                
src/azure-cli-testsdk/azure/cli/testsdk/base.py:177: in cmd
    return execute(self.cli_ctx, command, expect_failure=expect_failure).assert_with_checks(checks)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-testsdk/azure/cli/testsdk/base.py:252: in init
    self.in_process_execute(cli_ctx, command, expect_failure=expect_failure)
 
                                       

self = <azure.cli.testsdk.base.ExecutionResult object at 0x7f5e51dedbd0>
cli_ctx = <azure.cli.core.mock.DummyCli object at 0x7f5e53a26850>
command = 'ams account sp create -a ams000003 -n ams000003-access-sp -g clitest.rg000001 -p spp1!000004 --role Owner'
expect_failure = False

    def _in_process_execute(self, cli_ctx, command, expect_failure=False):
        from io import StringIO
        from vcr.errors import CannotOverwriteExistingCassetteException
    
        if command.startswith('az '):
            command = command[3:]
    
        stdout_buf = StringIO()
        logging_buf = StringIO()
        try:
            # issue: stderr cannot be redirect in this form, as a result some failure information
            # is lost when command fails.
            self.exit_code = cli_ctx.invoke(shlex.split(command), out_file=stdout_buf) or 0
            self.output = stdout_buf.getvalue()
            self.applog = logging_buf.getvalue()
    
        except CannotOverwriteExistingCassetteException as ex:
>           raise AssertionError(ex)
E           AssertionError: Can't overwrite existing cassette ('/mnt/vss/_work/1/s/src/azure-cli/azure/cli/command_modules/ams/tests/latest/recordings/test_ams_sp_create_reset.yaml') in your current record mode ('once').
E           No match for the request (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01&$filter=principalId%20eq%20%2727e628bb-89e7-44f6-9193-9c3d4955dcff%27>)&nbsp;was&nbsp;found.
E           Found 2 similar requests with 0 different matcher(s) :
E           
E           1 - (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01&$filter=principalId%20eq%20'27e628bb-89e7-44f6-9193-9c3d4955dcff'>).
E           Matchers succeeded : ['method', 'scheme', 'host', 'port', 'path', '_custom_request_query_matcher']
E           Matchers failed :
E           
E           2 - (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01&$filter=principalId%20eq%20'27e628bb-89e7-44f6-9193-9c3d4955dcff'>).
E           Matchers succeeded : ['method', 'scheme', 'host', 'port', 'path', '_custom_request_query_matcher']
E           Matchers failed :

src/azure-cli-testsdk/azure/cli/testsdk/base.py:308: AssertionError
azure/cli/command_modules/ams/tests/latest/test_ams_sp_scenarios.py:12
🔄apim
🔄latest
🔄3.12
🔄3.13
🔄appconfig
🔄latest
🔄3.12
️✔️3.13
️✔️appservice
️✔️latest
️✔️3.12
️✔️3.13
🔄aro
🔄latest
🔄3.12
🔄3.13
🔄backup
🔄latest
🔄3.12
️✔️3.13
️✔️batch
️✔️latest
️✔️3.12
️✔️3.13
️✔️batchai
️✔️latest
️✔️3.12
️✔️3.13
🔄billing
🔄latest
🔄3.12
️✔️3.13
️✔️botservice
️✔️latest
️✔️3.12
️✔️3.13
🔄cdn
🔄latest
🔄3.12
🔄3.13
🔄cloud
🔄latest
🔄3.12
🔄3.13
🔄cognitiveservices
🔄latest
🔄3.12
️✔️3.13
️✔️compute_recommender
️✔️latest
️✔️3.12
️✔️3.13
️✔️computefleet
️✔️latest
️✔️3.12
️✔️3.13
️✔️config
️✔️latest
️✔️3.12
️✔️3.13
🔄configure
🔄latest
🔄3.12
️✔️3.13
️✔️consumption
️✔️latest
️✔️3.12
️✔️3.13
️✔️container
️✔️latest
️✔️3.12
️✔️3.13
🔄containerapp
🔄latest
🔄3.12
🔄3.13
🔄core
🔄latest
🔄3.12
️✔️3.13
🔄cosmosdb
🔄latest
🔄3.12
🔄3.13
🔄databoxedge
🔄latest
🔄3.12
️✔️3.13
🔄dls
🔄latest
🔄3.12
️✔️3.13
🔄dms
🔄latest
🔄3.12
️✔️3.13
🔄eventgrid
🔄latest
🔄3.12
🔄3.13
🔄eventhubs
🔄latest
🔄3.12
🔄3.13
️✔️feedback
️✔️latest
️✔️3.12
️✔️3.13
🔄find
🔄latest
🔄3.12
🔄3.13
🔄hdinsight
🔄latest
🔄3.12
️✔️3.13
🔄identity
🔄latest
🔄3.12
🔄3.13
❌iot
❌latest
🔄3.12
❌3.13
Type Test Case Error Message Line
Failed test_identity_hub self = <azure.cli.testsdk.base.ExecutionResult object at 0x7fb5ab1965d0>
cli_ctx = <azure.cli.core.mock.DummyCli object at 0x7fb5ae5e0cd0>
command = 'role assignment create --role "Azure Event Hubs Data Sender" --assignee "2513c08b-66af-4cc3-ae78-3c21f7c25c25" --scop...g000001/providers/Microsoft.EventHub/namespaces/ehNamespaceiothubfortest1000009/eventhubs/eventHubiothubfortest000010"'
expect_failure = False

    def in_process_execute(self, cli_ctx, command, expect_failure=False):
        from io import StringIO
        from vcr.errors import CannotOverwriteExistingCassetteException
    
        if command.startswith('az '):
            command = command[3:]
    
        stdout_buf = StringIO()
        logging_buf = StringIO()
        try:
            # issue: stderr cannot be redirect in this form, as a result some failure information
            # is lost when command fails.
>           self.exit_code = cli_ctx.invoke(shlex.split(command), out_file=stdout_buf) or 0
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

src/azure-cli-testsdk/azure/cli/testsdk/base.py:303: 
                                        
env/lib/python3.13/site-packages/knack/cli.py:245: in invoke
    exit_code = self.exception_handler(ex)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/init.py:157: in exception_handler
    return handle_exception(ex)
           ^^^^^^^^^^^^^^^^^^^^
src/azure-cli-testsdk/azure/cli/testsdk/patches.py:33: in handle_main_exception
    raise ex
env/lib/python3.13/site-packages/knack/cli.py:233: in invoke
    cmd_result = self.invocation.execute(args)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:677: in execute
    raise ex
src/azure-cli-core/azure/cli/core/commands/init.py:820: in run_jobs_serially
    results.append(self.run_job(expanded_arg, cmd_copy))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:789: in run_job
    result = cmd_copy(params)
             ^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:335: in call
    return self.handler(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/command_operation.py:120: in handler
    return op(**command_args)
           ^^^^^^^^^^^^^^^^^^
src/azure-cli/azure/cli/command_modules/role/custom.py:198: in create_role_assignment
    return create_role_assignment(cmd.cli_ctx, role, object_id, scope=scope, resolve_assignee=False,
src/azure-cli/azure/cli/command_modules/role/custom.py:222: in create_role_assignment
    return worker.create_role_assignment(assignments_client, assignment_name, role_id, object_id, scope,
src/azure-cli/azure/cli/command_modules/role/custom.py:2206: in create_role_assignment
    return client.create(scope, assignment_name, parameters)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/tracing/decorator.py:119: in wrapper_use_tracer
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/mgmt/authorization/operations/operations.py:10022: in create
    pipeline_response: PipelineResponse = self.client.pipeline.run(  # pylint: disable=protected-access
env/lib/python3.13/site-packages/azure/core/pipeline/base.py:242: in run
    return first_node.send(pipeline_request)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/mgmt/core/policies/base.py:95: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/policies/redirect.py:205: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/policies/retry.py:545: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/policies/authentication.py:194: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/base.py:130: in send
    self.sender.send(request.http_request, **request.context.options),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/pipeline/transport/requests_basic.py:375: in send
    response = self.session.request(  # type: ignore
env/lib/python3.13/site-packages/requests/sessions.py:592: in request
    resp = self.send(prep, **send_kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/requests/sessions.py:706: in send
    r = adapter.send(request, **kwargs)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/requests/adapters.py:645: in send
    resp = conn.urlopen(
env/lib/python3.13/site-packages/urllib3/connectionpool.py:787: in urlopen
    response = self.make_request(
env/lib/python3.13/site-packages/urllib3/connectionpool.py:534: in make_request
    response = conn.getresponse()
               ^^^^^^^^^^^^^^^^^^
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
           

self = <VCRRequestsHTTPSConnection/mnt/vss/work/1/s/src/azure-cli/azure/cli/command_modules/iot/tests/latest/recordings/test_identity_hub.yaml(host='management.azure.com', port=443) at 0x7fb5aafeeeb0>
 = False, kwargs = {}

    def getresponse(self, =False, **kwargs):
        """Retrieve the response"""
        # Check to see if the cassette has a response for this request. If so,
        # then return it
        if self.cassette.can_play_response_for(self.vcr_request):
            log.info(f"Playing response for {self.vcr_request} from cassette")
            response = self.cassette.play_response(self.vcr_request)
            return VCRHTTPResponse(response)
        else:
            if self.cassette.write_protected and self.cassette.filter_request(self.vcr_request):
>               raise CannotOverwriteExistingCassetteException(
                    cassette=self.cassette,
                    failed_request=self.vcr_request,
                )
E               vcr.errors.CannotOverwriteExistingCassetteException: Can't overwrite existing cassette ('/mnt/vss/work/1/s/src/azure-cli/azure/cli/command_modules/iot/tests/latest/recordings/test_identity_hub.yaml') in your current record mode ('once').
E               No match for the request (<Request (PUT) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest.rg000001/providers/Microsoft.EventHub/namespaces/ehNamespaceiothubfortest1000009/eventhubs/eventHubiothubfortest000010/providers/Microsoft.Authorization/roleAssignments/88888888-0000-0000-0000-000000000001?api-version=2022-04-01>)&nbsp;was&nbsp;found.
E               Found 3 similar requests with 1 different matcher(s) :
E               
E               1 - (<Request (PUT) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest.rg000001/providers/Microsoft.Storage/storageAccounts/clitest000002/providers/Microsoft.Authorization/roleAssignments/88888888-0000-0000-0000-000000000001?api-version=2022-04-01>).
E               Matchers succeeded : ['method', 'scheme', 'host', 'port', 'custom_request_query_matcher']
E               Matchers failed :
E               path - assertion failure :
E               /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest.rg000001/providers/Microsoft.EventHub/namespaces/ehNamespaceiothubfortest1000009/eventhubs/eventHubiothubfortest000010/providers/Microsoft.Authorization/roleAssignments/88888888-0000-0000-0000-000000000001 != /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest.rg000001/providers/Microsoft.Storage/storageAccounts/clitest000002/providers/Microsoft.Authorization/roleAssignments/88888888-0000-0000-0000-000000000001
E               
E               2 - (<Request (PUT) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest.rg000001/providers/Microsoft.EventHub/namespaces/ehNamespaceiothubfortest1000009/eventhubs/eventHubiothubfortest000010/providers/Microsoft.Authorization/roleAssignments/88888888-0000-0000-0000-000000000002?api-version=2022-04-01>).
E               Matchers succeeded : ['method', 'scheme', 'host', 'port', 'custom_request_query_matcher']
E               Matchers failed :
E               path - assertion failure :
E               /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest.rg000001/providers/Microsoft.EventHub/namespaces/ehNamespaceiothubfortest1000009/eventhubs/eventHubiothubfortest000010/providers/Microsoft.Authorization/roleAssignments/88888888-0000-0000-0000-000000000001 != /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest.rg000001/providers/Microsoft.EventHub/namespaces/ehNamespaceiothubfortest1000009/eventhubs/eventHubiothubfortest000010/providers/Microsoft.Authorization/roleAssignments/88888888-0000-0000-0000-000000000002
E               
E               3 - (<Request (PUT) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest.rg000001/providers/Microsoft.EventHub/namespaces/ehNamespaceiothubfortest1000009/eventhubs/eventHubiothubfortest000010/providers/Microsoft.Authorization/roleAssignments/88888888-0000-0000-0000-000000000003?api-version=2022-04-01>).
E               Matchers succeeded : ['method', 'scheme', 'host', 'port', 'custom_request_query_matcher']
E               Matchers failed :
E               path - assertion failure :
E               /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest.rg000001/providers/Microsoft.EventHub/namespaces/ehNamespaceiothubfortest1000009/eventhubs/eventHubiothubfortest000010/providers/Microsoft.Authorization/roleAssignments/88888888-0000-0000-0000-000000000001 != /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest.rg000001/providers/Microsoft.EventHub/namespaces/ehNamespaceiothubfortest1000009/eventhubs/eventHubiothubfortest000010/providers/Microsoft.Authorization/roleAssignments/88888888-0000-0000-0000-000000000003

env/lib/python3.13/site-packages/vcr/stubs/init.py:277: CannotOverwriteExistingCassetteException

During handling of the above exception, another exception occurred:

self = <azure.cli.command_modules.iot.tests.latest.test_iot_commands.IoTHubTest testMethod=test_identity_hub>
resource_group = 'clitest.rg000001', resource_group_location = 'westus2'
storage_account = 'clitest000002'

    @AllowLargeResponse()
    @ResourceGroupPreparer(location='westus2')
    @StorageAccountPreparer()
    def test_identity_hub(self, resource_group, resource_group_location, storage_account):
        # Test IoT Hub create with identity
        from time import sleep
    
        subscription_id = self.get_subscription_id()
        rg = resource_group
        location = resource_group_location
    
        private_endpoint_type = 'Microsoft.Devices/IoTHubs'
        identity_hub = self.create_random_name(prefix='identitytesthub', length=32)
        identity_based_auth = 'identityBased'
        event_hub_system_identity_endpoint_name = self.create_random_name(prefix='EHSystemIdentityEndpoint', length=32)
        event_hub_user_identity_endpoint_name = self.create_random_name(prefix='EHUserIdentityEndpoint', length=32)
    
        containerName = 'iothubcontainer'
        storageConnectionString = self.get_azurestorage_connectionstring(rg, containerName, storage_account)
        endpoint_name = 'Event1'
        endpoint_type = 'EventHub'
        storage_cs_pattern = 'DefaultEndpointsProtocol=https;EndpointSuffix=core.windows.net;'
    
        identity_storage_role = 'Storage Blob Data Contributor'
        storage_account_id = self.cmd('storage account show -n {0} -g {1}'.format(storage_account, rg)).get_output_in_json()['id']
    
        # identities
        user_identity_names = [
            self.create_random_name(prefix='iot-user-identity', length=32),
            self.create_random_name(prefix='iot-user-identity', length=32),
            self.create_random_name(prefix='iot-user-identity', length=32)
        ]
    
        # create user-assigned identity
        with mock.patch('azure.cli.command_modules.role.custom.gen_guid', side_effect=self.create_guid):
            user_identity_1 = self.cmd('identity create -n {0} -g {1}'.format(user_identity_names[0], rg)).get_output_in_json()['id']
            user_identity_2 = self.cmd('identity create -n {0} -g {1}'.format(user_identity_names[1], rg)).get_output_in_json()['id']
            user_identity_3 = self.cmd('identity create -n {0} -g {1}'.format(user_identity_names[2], rg)).get_output_in_json()['id']
    
        # create hub with system-assigned identity, user-assigned identity, and assign storage roles
        with mock.patch('azure.cli.core.commands.arm.gen_guid', side_effect=self.create_guid):
            self.cmd('iot hub create -n {0} -g {1} --sku s1 --location {2} --mintls "1.2" --mi-system-assigned --mi-user-assigned {3} --role "{4}" --scopes "{5}"'
                     .format(identity_hub, rg, location, user_identity_1, identity_storage_role, storage_account_id))
    
        hub_props = self.cmd('iot hub show --name {0}'.format(identity_hub), checks=[
            self.check('properties.minTlsVersion', '1.2'),
            self.check('identity.type', 'SystemAssigned, UserAssigned')]).get_output_in_json()
    
        hub_object_id = hub_props['identity']['principalId']
        assert hub_object_id
    
        # Allow time for RBAC and Identity Service
        if self.is_live:
            sleep(60)
    
        # Test 'az iot hub update' with Identity-based fileUpload
        updated_hub = self.cmd('iot hub update -n {0} --fsa {1} --fsi [system] --fcs {2} --fc {3} --fnld 15'
                               .format(identity_hub, identity_based_auth, storageConnectionString, containerName)).get_output_in_json()
        assert updated_hub['properties']['storageEndpoints']['$default']['authenticationType'] == identity_based_auth
        assert updated_hub['properties']['messagingEndpoints']['fileNotifications']['lockDurationAsIso8601'] == '0:00:15'
        assert storage_cs_pattern in updated_hub['properties']['storageEndpoints']['$default']['connectionString']
        # Test fileupload authentication type settings
        # Setting key-based file upload (identity based commands should fail)
        updated_hub = self.cmd('iot hub update -n {0} -g {1} --fsa keyBased'.format(identity_hub, rg)).get_output_in_json()
        assert updated_hub['properties']['storageEndpoints']['$default']['authenticationType'] == 'keyBased'
        updated_hub = self.cmd('iot hub update -n {0} -g {1} --fsi test/user/'.format(identity_hub, rg), expect_failure=True)
        updated_hub = self.cmd('iot hub update -n {0} -g {1} --fsi [system]'.format(identity_hub, rg), expect_failure=True)
    
        # Back to identity-based file upload
        updated_hub = self.cmd('iot hub update -n {0} -g {1} --fsa {2}'.format(identity_hub, rg, identity_based_auth)).get_output_in_json()
        assert updated_hub['properties']['storageEndpoints']['$default']['authenticationType'] == identity_based_auth
    
        # Create EH and link identity
>       eh_info = self.create_eventhub_and_link_identity(rg, hub_object_id, [user_identity_1])
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

src/azure-cli/azure/cli/command_modules/iot/tests/latest/test_iot_commands.py:515: 
 
 
 
 
 
 
 
 
 
 
 
 
 
                           
src/azure-cli/azure/cli/command_modules/iot/tests/latest/test_iot_commands.py:906: in create_eventhub_and_link_identity
    self.cmd('role assignment create --role "{0}" --assignee "{1}" --scope "{2}"'.format(role, hub_object_id, eh['id']))
src/azure-cli-testsdk/azure/cli/testsdk/base.py:177: in cmd
    return execute(self.cli_ctx, command, expect_failure=expect_failure).assert_with_checks(checks)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-testsdk/azure/cli/testsdk/base.py:252: in init
    self.in_process_execute(cli_ctx, command, expect_failure=expect_failure)
 
                                       

self = <azure.cli.testsdk.base.ExecutionResult object at 0x7fb5ab1965d0>
cli_ctx = <azure.cli.core.mock.DummyCli object at 0x7fb5ae5e0cd0>
command = 'role assignment create --role "Azure Event Hubs Data Sender" --assignee "2513c08b-66af-4cc3-ae78-3c21f7c25c25" --scop...g000001/providers/Microsoft.EventHub/namespaces/ehNamespaceiothubfortest1000009/eventhubs/eventHubiothubfortest000010"'
expect_failure = False

    def _in_process_execute(self, cli_ctx, command, expect_failure=False):
        from io import StringIO
        from vcr.errors import CannotOverwriteExistingCassetteException
    
        if command.startswith('az '):
            command = command[3:]
    
        stdout_buf = StringIO()
        logging_buf = StringIO()
        try:
            # issue: stderr cannot be redirect in this form, as a result some failure information
            # is lost when command fails.
            self.exit_code = cli_ctx.invoke(shlex.split(command), out_file=stdout_buf) or 0
            self.output = stdout_buf.getvalue()
            self.applog = logging_buf.getvalue()
    
        except CannotOverwriteExistingCassetteException as ex:
>           raise AssertionError(ex)
E           AssertionError: Can't overwrite existing cassette ('/mnt/vss/_work/1/s/src/azure-cli/azure/cli/command_modules/iot/tests/latest/recordings/test_identity_hub.yaml') in your current record mode ('once').
E           No match for the request (<Request (PUT) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest.rg000001/providers/Microsoft.EventHub/namespaces/ehNamespaceiothubfortest1000009/eventhubs/eventHubiothubfortest000010/providers/Microsoft.Authorization/roleAssignments/88888888-0000-0000-0000-000000000001?api-version=2022-04-01>)&nbsp;was&nbsp;found.
E           Found 3 similar requests with 1 different matcher(s) :
E           
E           1 - (<Request (PUT) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest.rg000001/providers/Microsoft.Storage/storageAccounts/clitest000002/providers/Microsoft.Authorization/roleAssignments/88888888-0000-0000-0000-000000000001?api-version=2022-04-01>).
E           Matchers succeeded : ['method', 'scheme', 'host', 'port', '_custom_request_query_matcher']
E           Matchers failed :
E           path - assertion failure :
E           /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest.rg000001/providers/Microsoft.EventHub/namespaces/ehNamespaceiothubfortest1000009/eventhubs/eventHubiothubfortest000010/providers/Microsoft.Authorization/roleAssignments/88888888-0000-0000-0000-000000000001 != /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest.rg000001/providers/Microsoft.Storage/storageAccounts/clitest000002/providers/Microsoft.Authorization/roleAssignments/88888888-0000-0000-0000-000000000001
E           
E           2 - (<Request (PUT) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest.rg000001/providers/Microsoft.EventHub/namespaces/ehNamespaceiothubfortest1000009/eventhubs/eventHubiothubfortest000010/providers/Microsoft.Authorization/roleAssignments/88888888-0000-0000-0000-000000000002?api-version=2022-04-01>).
E           Matchers succeeded : ['method', 'scheme', 'host', 'port', '_custom_request_query_matcher']
E           Matchers failed :
E           path - assertion failure :
E           /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest.rg000001/providers/Microsoft.EventHub/namespaces/ehNamespaceiothubfortest1000009/eventhubs/eventHubiothubfortest000010/providers/Microsoft.Authorization/roleAssignments/88888888-0000-0000-0000-000000000001 != /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest.rg000001/providers/Microsoft.EventHub/namespaces/ehNamespaceiothubfortest1000009/eventhubs/eventHubiothubfortest000010/providers/Microsoft.Authorization/roleAssignments/88888888-0000-0000-0000-000000000002
E           
E           3 - (<Request (PUT) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest.rg000001/providers/Microsoft.EventHub/namespaces/ehNamespaceiothubfortest1000009/eventhubs/eventHubiothubfortest000010/providers/Microsoft.Authorization/roleAssignments/88888888-0000-0000-0000-000000000003?api-version=2022-04-01>).
E           Matchers succeeded : ['method', 'scheme', 'host', 'port', '_custom_request_query_matcher']
E           Matchers failed :
E           path - assertion failure :
E           /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest.rg000001/providers/Microsoft.EventHub/namespaces/ehNamespaceiothubfortest1000009/eventhubs/eventHubiothubfortest000010/providers/Microsoft.Authorization/roleAssignments/88888888-0000-0000-0000-000000000001 != /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest.rg000001/providers/Microsoft.EventHub/namespaces/ehNamespaceiothubfortest1000009/eventhubs/eventHubiothubfortest000010/providers/Microsoft.Authorization/roleAssignments/88888888-0000-0000-0000-000000000003

src/azure-cli-testsdk/azure/cli/testsdk/base.py:308: AssertionError
azure/cli/command_modules/iot/tests/latest/test_iot_commands.py:441
️✔️keyvault
️✔️latest
️✔️3.12
️✔️3.13
️✔️lab
️✔️latest
️✔️3.12
️✔️3.13
️✔️managedservices
️✔️latest
️✔️3.12
️✔️3.13
️✔️maps
️✔️latest
️✔️3.12
️✔️3.13
🔄marketplaceordering
🔄latest
🔄3.12
️✔️3.13
🔄monitor
🔄latest
🔄3.12
🔄3.13
🔄mysql
🔄latest
🔄3.12
️✔️3.13
🔄netappfiles
🔄latest
🔄3.12
️✔️3.13
️✔️network
️✔️latest
️✔️3.12
️✔️3.13
🔄policyinsights
🔄latest
🔄3.12
️✔️3.13
🔄postgresql
🔄latest
🔄3.12
🔄3.13
🔄privatedns
🔄latest
🔄3.12
🔄3.13
🔄profile
🔄latest
🔄3.12
🔄3.13
🔄rdbms
🔄latest
🔄3.12
️✔️3.13
️✔️redis
️✔️latest
️✔️3.12
️✔️3.13
🔄relay
🔄latest
🔄3.12
️✔️3.13
🔄resource
🔄latest
🔄3.12
🔄3.13
❌role
❌latest
❌3.12
Type Test Case Error Message Line
Failed test_create_for_rbac_password_with_assignment self = <azure.cli.testsdk.base.ExecutionResult object at 0x7f84d0ac3320>
cli_ctx = <azure.cli.core.mock.DummyCli object at 0x7f84d2f40ef0>
command = 'role assignment list --assignee 6002bdc3-7496-4654-aa6a-99454f15a071 --all'
expect_failure = False

    def in_process_execute(self, cli_ctx, command, expect_failure=False):
        from io import StringIO
        from vcr.errors import CannotOverwriteExistingCassetteException
    
        if command.startswith('az '):
            command = command[3:]
    
        stdout_buf = StringIO()
        logging_buf = StringIO()
        try:
            # issue: stderr cannot be redirect in this form, as a result some failure information
            # is lost when command fails.
>           self.exit_code = cli_ctx.invoke(shlex.split(command), out_file=stdout_buf) or 0
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

src/azure-cli-testsdk/azure/cli/testsdk/base.py:303: 
                                        
env/lib/python3.12/site-packages/knack/cli.py:245: in invoke
    exit_code = self.exception_handler(ex)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/init.py:157: in exception_handler
    return handle_exception(ex)
           ^^^^^^^^^^^^^^^^^^^^
                                        

ex = KeyError('roleDefinitionId'), args = (), kwargs = {}

    def handle_main_exception(ex, *args, **kwargs):  # pylint: disable=unused-argument
        if isinstance(ex, CannotOverwriteExistingCassetteException):
            # This exception usually caused by a no match HTTP request. This is a product error
            # that is caused by change of SDK invocation.
            raise ex
    
>       raise CliExecutionError(ex)
E       azure.cli.testsdk.exceptions.CliExecutionError: The CLI throws exception KeyError during execution and fails the command.

src/azure-cli-testsdk/azure/cli/testsdk/patches.py:35: CliExecutionError

During handling of the above exception, another exception occurred:

self = <azure.cli.command_modules.role.tests.latest.test_role.CreateForRbacScenarioTest testMethod=test_create_for_rbac_password_with_assignment>
resource_group = 'cli_sp_create_for_rbac000001'

    @AllowLargeResponse()
    @ResourceGroupPreparer(name_prefix='cli_sp_create_for_rbac')
    def test_create_for_rbac_password_with_assignment(self, resource_group):
    
        subscription_id = self.get_subscription_id()
        self.kwargs.update({
            'sub': subscription_id,
            'scope': f'/subscriptions/{subscription_id}/resourceGroups/{resource_group}',
            'role': 'Reader',
            'display_name': resource_group
        })
    
        with mock.patch('azure.cli.command_modules.role.custom.gen_guid', side_effect=self.create_guid):
            result = self.cmd('ad sp create-for-rbac -n {display_name} --scopes {scope} --role {role}',
                              checks=self.check('displayName', '{display_name}')).get_output_in_json()
            self.kwargs['app_id'] = result['appId']
>           result = self.cmd(
                'role assignment list --assignee {app_id} --all',
                checks=[
                    self.check("length([])", 1),
                    self.check("[0].roleDefinitionName", '{role}'),
                    self.check("[0].scope", '{scope}')
                ]).get_output_in_json()

src/azure-cli/azure/cli/command_modules/role/tests/latest/test_role.py:173: 
 
 
                                      
src/azure-cli-testsdk/azure/cli/testsdk/base.py:177: in cmd
    return execute(self.cli_ctx, command, expect_failure=expect_failure).assert_with_checks(checks)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-testsdk/azure/cli/testsdk/base.py:252: in init
    self.in_process_execute(cli_ctx, command, expect_failure=expect_failure)
src/azure-cli-testsdk/azure/cli/testsdk/base.py:315: in in_process_execute
    raise ex.exception
env/lib/python3.12/site-packages/knack/cli.py:233: in invoke
    cmd_result = self.invocation.execute(args)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:677: in execute
    raise ex
src/azure-cli-core/azure/cli/core/commands/init.py:820: in run_jobs_serially
    results.append(self.run_job(expanded_arg, cmd_copy))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:789: in run_job
    result = cmd_copy(params)
             ^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:335: in call
    return self.handler(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/command_operation.py:120: in handler
    return op(**command_args)
           ^^^^^^^^^^^^^^^^^^
 
 
 
 
 
                                   

cmd = <azure.cli.core.commands.AzCliCommand object at 0x7f84d0a570e0>
assignee = '6002bdc3-7496-4654-aa6a-99454f15a071'
assignee_object_id = '0207ee96-75b6-43d8-af87-f8718fde0acc', role = None
resource_group_name = None, scope = None, include_inherited = False
show_all = True, include_groups = False, fill_role_definition_name = True
fill_principal_name = True

    def list_role_assignments(cmd,  # pylint: disable=too-many-locals, too-many-branches
                              assignee=None, assignee_object_id=None,
                              role=None,
                              resource_group_name=None, scope=None,
                              include_inherited=False,
                              show_all=False, include_groups=False,
                              fill_role_definition_name=True, fill_principal_name=True):
        if assignee and assignee_object_id:
            raise CLIError('Usage error: Provide only one of --assignee or --assignee-object-id.')
    
        graph_client = _graph_client_factory(cmd.cli_ctx)
        authorization_client = _auth_client_factory(cmd.cli_ctx, scope)
        assignments_client = authorization_client.role_assignments
        definitions_client = authorization_client.role_definitions
    
        if show_all:
            if resource_group_name or scope:
                raise CLIError('group or scope are not required when --all is used')
            scope = None
        else:
            scope = _build_role_scope(resource_group_name, scope,
                                      definitions_client._config.subscription_id)
    
        if assignee and not assignee_object_id:
            assignee_object_id = _resolve_object_id(cmd.cli_ctx, assignee, fallback_to_object_id=True)
        assignments = _search_role_assignments(assignments_client, definitions_client,
                                               scope, assignee_object_id, role,
                                               include_inherited, include_groups)
    
        results = todict(assignments) if assignments else []
    
        if not results:
            return []
    
        # Fill in role definition names
        if fill_role_definition_name:
            role_defs = list(definitions_client.list(
                scope=scope or ('/subscriptions/' + definitions_client._config.subscription_id)))
            role_dics = {rd.id: rd.role_name for rd in role_defs}
            for ra in results:
>               ra['roleDefinitionName'] = role_dics.get(ra['roleDefinitionId'])
                                                         ^^^^^^^^^^^^^^^^^^^^^^
E               KeyError: 'roleDefinitionId'

src/azure-cli/azure/cli/command_modules/role/custom.py:267: KeyError
azure/cli/command_modules/role/tests/latest/test_role.py:156
Failed test_role_assignment_create_update self = <azure.cli.testsdk.base.ExecutionResult object at 0x7f84cf7e92b0>
cli_ctx = <azure.cli.core.mock.DummyCli object at 0x7f84d2f30410>
command = 'role assignment update --role-assignment '{"condition": "@resource[Microsoft.Storage/storageAccounts/blobServices/co...eAssignments", "updatedBy": "6d97229a-391f-473a-893f-f0608b592d7b", "updatedOn": "2023-08-23T13:05:57.662192+00:00"}''
expect_failure = False

    def in_process_execute(self, cli_ctx, command, expect_failure=False):
        from io import StringIO
        from vcr.errors import CannotOverwriteExistingCassetteException
    
        if command.startswith('az '):
            command = command[3:]
    
        stdout_buf = StringIO()
        logging_buf = StringIO()
        try:
            # issue: stderr cannot be redirect in this form, as a result some failure information
            # is lost when command fails.
>           self.exit_code = cli_ctx.invoke(shlex.split(command), out_file=stdout_buf) or 0
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

src/azure-cli-testsdk/azure/cli/testsdk/base.py:303: 
                                        
env/lib/python3.12/site-packages/knack/cli.py:245: in invoke
    exit_code = self.exception_handler(ex)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/init.py:157: in exception_handler
    return handle_exception(ex)
           ^^^^^^^^^^^^^^^^^^^^
                                        

ex = ValueError('No value for given attribute'), args = (), kwargs = {}

    def handle_main_exception(ex, *args, **kwargs):  # pylint: disable=unused-argument
        if isinstance(ex, CannotOverwriteExistingCassetteException):
            # This exception usually caused by a no match HTTP request. This is a product error
            # that is caused by change of SDK invocation.
            raise ex
    
>       raise CliExecutionError(ex)
E       azure.cli.testsdk.exceptions.CliExecutionError: The CLI throws exception ValueError during execution and fails the command.

src/azure-cli-testsdk/azure/cli/testsdk/patches.py:35: CliExecutionError

During handling of the above exception, another exception occurred:

self = <azure.cli.command_modules.role.tests.latest.test_role.RoleAssignmentScenarioTest testMethod=test_role_assignment_create_update>
resource_group = 'cli_role_assign000001'

    @ResourceGroupPreparer(name_prefix='cli_role_assign')
    @AllowLargeResponse()
    def test_role_assignment_create_update(self, resource_group):
        if self.run_under_service_principal():
            return  # this test delete users which are beyond a SP's capacity, so quit...
    
        with mock.patch('azure.cli.command_modules.role.custom.gen_guid', side_effect=self.create_guid):
            user = self.create_random_name('testuser', 15)
            self.kwargs.update({
                'upn': user + TEST_TENANT_DOMAIN,
                'description': "Role assignment foo to check on bar",
                'condition': "@resource[Microsoft.Storage/storageAccounts/blobServices/containers:Name] stringEquals 'foo'",
                'condition_version': "2.0"
            })
            self.prepare_scope_kwargs()
    
            result = self.cmd('ad user create --display-name tester123 --password Test123456789 --user-principal-name {upn}').get_output_in_json()
            self.kwargs['object_id'] = result['id']
            try:
                # Test create role assignment with description, condition and condition_version
                self.cmd('role assignment create --assignee-object-id {object_id} --assignee-principal-type User --role reader --scope {rg_id} '
                         # Include double quotes to tell shlex to treat arguments as a whole
                         '--description "{description}" '
                         '--condition "{condition}" --condition-version {condition_version}',
                         checks=[
                             self.check("description", "{description}"),
                             self.check("condition", "{condition}"),
                             self.check("conditionVersion", "{condition_version}")
                         ])
                self.cmd('role assignment delete -g {rg}')
    
                # Test create role assignment with description, condition. condition_version defaults to 2.0
                self.cmd('role assignment create --assignee-object-id {object_id} --assignee-principal-type User --role reader --scope {rg_id} '
                         '--description "{description}" '
                         '--condition "{condition}"',
                         checks=[
                             self.check("description", "{description}"),
                             self.check("condition", "{condition}"),
                             self.check("conditionVersion", "2.0")
                         ])
                self.cmd('role assignment delete -g {rg}')
    
                # Test error is raised if condition-version is set but condition is not
                with self.assertRaisesRegex(CLIError, "--condition must be set"):
                    self.cmd('role assignment create --assignee-object-id {object_id} --assignee-principal-type User --role reader --scope {rg_id} '
                             '--condition-version {condition_version}')
    
                # Update
                output = self.cmd('role assignment create --assignee-object-id {object_id} --assignee-principal-type User --role reader --scope {rg_id} '
                                  '--description "{description}" '
                                  '--condition "{condition}" --condition-version {condition_version}').get_output_in_json()
    
                updated_description = "Some updated description."
                output['description'] = updated_description
                import shlex
                from ..util import escape_apply_kwargs
                # The json contains both single (in description) and double quotes, use shlex to quote and escape it,
                # then escape it again before being passed to self.cmd
                output_json = escape_apply_kwargs(shlex.quote(json.dumps(output)))
    
>               self.cmd("role assignment update --role-assignment {}".format(output_json),
                         checks=[self.check("description", updated_description)])

src/azure-cli/azure/cli/command_modules/role/tests/latest/test_role.py:614: 
 
 
 
                                     
src/azure-cli-testsdk/azure/cli/testsdk/base.py:177: in cmd
    return execute(self.cli_ctx, command, expect_failure=expect_failure).assert_with_checks(checks)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-testsdk/azure/cli/testsdk/base.py:252: in init
    self.in_process_execute(cli_ctx, command, expect_failure=expect_failure)
src/azure-cli-testsdk/azure/cli/testsdk/base.py:315: in in_process_execute
    raise ex.exception
env/lib/python3.12/site-packages/knack/cli.py:233: in invoke
    cmd_result = self.invocation.execute(args)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:677: in execute
    raise ex
src/azure-cli-core/azure/cli/core/commands/init.py:820: in run_jobs_serially
    results.append(self.run_job(expanded_arg, cmd_copy))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:789: in run_job
    result = cmd_copy(params)
             ^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:335: in call
    return self.handler(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/command_operation.py:120: in handler
    return op(**command_args)
           ^^^^^^^^^^^^^^^^^^
src/azure-cli/azure/cli/command_modules/role/custom.py:309: in update_role_assignment
    original_assignment = assignments_client.get(scope, name)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/tracing/decorator.py:119: in wrapper_use_tracer
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/mgmt/authorization/operations/operations.py:9836: in get
    request = build_role_assignments_get_request(
env/lib/python3.12/site-packages/azure/mgmt/authorization/operations/operations.py:1762: in build_role_assignments_get_request
    "scope": SERIALIZER.url("scope", scope, "str", skip_quote=True),
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/mgmt/authorization/utils/serialization.py:700: in url
    output = self.serialize_data(data, data_type, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 
 
 
 
 
 
 
 
                               

self = <azure.mgmt.authorization._utils.serialization.Serializer object at 0x7f84d2e6bbc0>
data = None, data_type = 'str', kwargs = {'skip_quote': True}

    def serialize_data(self, data, data_type, **kwargs):
        """Serialize generic data according to supplied data type.
    
        :param object data: The data to be serialized.
        :param str data_type: The type to be serialized from.
        :raises AttributeError: if required data is None.
        :raises ValueError: if data is None
        :raises SerializationError: if serialization fails.
        :returns: The serialized data.
        :rtype: str, int, float, bool, dict, list
        """
        if data is None:
>           raise ValueError("No value for given attribute")
E           ValueError: No value for given attribute

env/lib/python3.12/site-packages/azure/mgmt/authorization/_utils/serialization.py:777: ValueError
azure/cli/command_modules/role/tests/latest/test_role.py:553
Failed test_role_assignment_create_using_principal_type The error message is too long, please check the pipeline log for details. azure/cli/command_modules/role/tests/latest/test_role.py:459
Failed test_role_assignment_scenario The error message is too long, please check the pipeline log for details. azure/cli/command_modules/role/tests/latest/test_role.py:322
Failed test_role_assignment_handle_conflicted_assignments The error message is too long, please check the pipeline log for details. azure/cli/command_modules/role/tests/latest/test_role.py:765
❌3.13
Type Test Case Error Message Line
Failed test_create_for_rbac_password_with_assignment self = <azure.cli.testsdk.base.ExecutionResult object at 0x7fa95cc9afd0>
cli_ctx = <azure.cli.core.mock.DummyCli object at 0x7fa95ef97d90>
command = 'role assignment list --assignee 6002bdc3-7496-4654-aa6a-99454f15a071 --all'
expect_failure = False

    def in_process_execute(self, cli_ctx, command, expect_failure=False):
        from io import StringIO
        from vcr.errors import CannotOverwriteExistingCassetteException
    
        if command.startswith('az '):
            command = command[3:]
    
        stdout_buf = StringIO()
        logging_buf = StringIO()
        try:
            # issue: stderr cannot be redirect in this form, as a result some failure information
            # is lost when command fails.
>           self.exit_code = cli_ctx.invoke(shlex.split(command), out_file=stdout_buf) or 0
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

src/azure-cli-testsdk/azure/cli/testsdk/base.py:303: 
                                        
env/lib/python3.13/site-packages/knack/cli.py:245: in invoke
    exit_code = self.exception_handler(ex)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/init.py:157: in exception_handler
    return handle_exception(ex)
           ^^^^^^^^^^^^^^^^^^^^
                                        

ex = KeyError('roleDefinitionId'), args = (), kwargs = {}

    def handle_main_exception(ex, *args, **kwargs):  # pylint: disable=unused-argument
        if isinstance(ex, CannotOverwriteExistingCassetteException):
            # This exception usually caused by a no match HTTP request. This is a product error
            # that is caused by change of SDK invocation.
            raise ex
    
>       raise CliExecutionError(ex)
E       azure.cli.testsdk.exceptions.CliExecutionError: The CLI throws exception KeyError during execution and fails the command.

src/azure-cli-testsdk/azure/cli/testsdk/patches.py:35: CliExecutionError

During handling of the above exception, another exception occurred:

self = <azure.cli.command_modules.role.tests.latest.test_role.CreateForRbacScenarioTest testMethod=test_create_for_rbac_password_with_assignment>
resource_group = 'cli_sp_create_for_rbac000001'

    @AllowLargeResponse()
    @ResourceGroupPreparer(name_prefix='cli_sp_create_for_rbac')
    def test_create_for_rbac_password_with_assignment(self, resource_group):
    
        subscription_id = self.get_subscription_id()
        self.kwargs.update({
            'sub': subscription_id,
            'scope': f'/subscriptions/{subscription_id}/resourceGroups/{resource_group}',
            'role': 'Reader',
            'display_name': resource_group
        })
    
        with mock.patch('azure.cli.command_modules.role.custom.gen_guid', side_effect=self.create_guid):
            result = self.cmd('ad sp create-for-rbac -n {display_name} --scopes {scope} --role {role}',
                              checks=self.check('displayName', '{display_name}')).get_output_in_json()
            self.kwargs['app_id'] = result['appId']
>           result = self.cmd(
                'role assignment list --assignee {app_id} --all',
                checks=[
                    self.check("length([])", 1),
                    self.check("[0].roleDefinitionName", '{role}'),
                    self.check("[0].scope", '{scope}')
                ]).get_output_in_json()

src/azure-cli/azure/cli/command_modules/role/tests/latest/test_role.py:173: 
 
 
                                      
src/azure-cli-testsdk/azure/cli/testsdk/base.py:177: in cmd
    return execute(self.cli_ctx, command, expect_failure=expect_failure).assert_with_checks(checks)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-testsdk/azure/cli/testsdk/base.py:252: in init
    self.in_process_execute(cli_ctx, command, expect_failure=expect_failure)
src/azure-cli-testsdk/azure/cli/testsdk/base.py:315: in in_process_execute
    raise ex.exception
env/lib/python3.13/site-packages/knack/cli.py:233: in invoke
    cmd_result = self.invocation.execute(args)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:677: in execute
    raise ex
src/azure-cli-core/azure/cli/core/commands/init.py:820: in run_jobs_serially
    results.append(self.run_job(expanded_arg, cmd_copy))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:789: in run_job
    result = cmd_copy(params)
             ^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:335: in call
    return self.handler(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/command_operation.py:120: in handler
    return op(**command_args)
           ^^^^^^^^^^^^^^^^^^
 
 
 
 
 
                                   

cmd = <azure.cli.core.commands.AzCliCommand object at 0x7fa95ccfe550>
assignee = '6002bdc3-7496-4654-aa6a-99454f15a071'
assignee_object_id = '0207ee96-75b6-43d8-af87-f8718fde0acc', role = None
resource_group_name = None, scope = None, include_inherited = False
show_all = True, include_groups = False, fill_role_definition_name = True
fill_principal_name = True

    def list_role_assignments(cmd,  # pylint: disable=too-many-locals, too-many-branches
                              assignee=None, assignee_object_id=None,
                              role=None,
                              resource_group_name=None, scope=None,
                              include_inherited=False,
                              show_all=False, include_groups=False,
                              fill_role_definition_name=True, fill_principal_name=True):
        if assignee and assignee_object_id:
            raise CLIError('Usage error: Provide only one of --assignee or --assignee-object-id.')
    
        graph_client = _graph_client_factory(cmd.cli_ctx)
        authorization_client = _auth_client_factory(cmd.cli_ctx, scope)
        assignments_client = authorization_client.role_assignments
        definitions_client = authorization_client.role_definitions
    
        if show_all:
            if resource_group_name or scope:
                raise CLIError('group or scope are not required when --all is used')
            scope = None
        else:
            scope = _build_role_scope(resource_group_name, scope,
                                      definitions_client._config.subscription_id)
    
        if assignee and not assignee_object_id:
            assignee_object_id = _resolve_object_id(cmd.cli_ctx, assignee, fallback_to_object_id=True)
        assignments = _search_role_assignments(assignments_client, definitions_client,
                                               scope, assignee_object_id, role,
                                               include_inherited, include_groups)
    
        results = todict(assignments) if assignments else []
    
        if not results:
            return []
    
        # Fill in role definition names
        if fill_role_definition_name:
            role_defs = list(definitions_client.list(
                scope=scope or ('/subscriptions/' + definitions_client._config.subscription_id)))
            role_dics = {rd.id: rd.role_name for rd in role_defs}
            for ra in results:
>               ra['roleDefinitionName'] = role_dics.get(ra['roleDefinitionId'])
                                                         ^^^^^^^^^^^^^^^^^^^^^^
E               KeyError: 'roleDefinitionId'

src/azure-cli/azure/cli/command_modules/role/custom.py:267: KeyError
azure/cli/command_modules/role/tests/latest/test_role.py:156
Failed test_role_assignment_create_update self = <azure.cli.testsdk.base.ExecutionResult object at 0x7fa95b9f1b50>
cli_ctx = <azure.cli.core.mock.DummyCli object at 0x7fa95edced50>
command = 'role assignment update --role-assignment '{"condition": "@resource[Microsoft.Storage/storageAccounts/blobServices/co...eAssignments", "updatedBy": "6d97229a-391f-473a-893f-f0608b592d7b", "updatedOn": "2023-08-23T13:05:57.662192+00:00"}''
expect_failure = False

    def in_process_execute(self, cli_ctx, command, expect_failure=False):
        from io import StringIO
        from vcr.errors import CannotOverwriteExistingCassetteException
    
        if command.startswith('az '):
            command = command[3:]
    
        stdout_buf = StringIO()
        logging_buf = StringIO()
        try:
            # issue: stderr cannot be redirect in this form, as a result some failure information
            # is lost when command fails.
>           self.exit_code = cli_ctx.invoke(shlex.split(command), out_file=stdout_buf) or 0
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

src/azure-cli-testsdk/azure/cli/testsdk/base.py:303: 
                                        
env/lib/python3.13/site-packages/knack/cli.py:245: in invoke
    exit_code = self.exception_handler(ex)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/init.py:157: in exception_handler
    return handle_exception(ex)
           ^^^^^^^^^^^^^^^^^^^^
                                        

ex = ValueError('No value for given attribute'), args = (), kwargs = {}

    def handle_main_exception(ex, *args, **kwargs):  # pylint: disable=unused-argument
        if isinstance(ex, CannotOverwriteExistingCassetteException):
            # This exception usually caused by a no match HTTP request. This is a product error
            # that is caused by change of SDK invocation.
            raise ex
    
>       raise CliExecutionError(ex)
E       azure.cli.testsdk.exceptions.CliExecutionError: The CLI throws exception ValueError during execution and fails the command.

src/azure-cli-testsdk/azure/cli/testsdk/patches.py:35: CliExecutionError

During handling of the above exception, another exception occurred:

self = <azure.cli.command_modules.role.tests.latest.test_role.RoleAssignmentScenarioTest testMethod=test_role_assignment_create_update>
resource_group = 'cli_role_assign000001'

    @ResourceGroupPreparer(name_prefix='cli_role_assign')
    @AllowLargeResponse()
    def test_role_assignment_create_update(self, resource_group):
        if self.run_under_service_principal():
            return  # this test delete users which are beyond a SP's capacity, so quit...
    
        with mock.patch('azure.cli.command_modules.role.custom.gen_guid', side_effect=self.create_guid):
            user = self.create_random_name('testuser', 15)
            self.kwargs.update({
                'upn': user + TEST_TENANT_DOMAIN,
                'description': "Role assignment foo to check on bar",
                'condition': "@resource[Microsoft.Storage/storageAccounts/blobServices/containers:Name] stringEquals 'foo'",
                'condition_version': "2.0"
            })
            self.prepare_scope_kwargs()
    
            result = self.cmd('ad user create --display-name tester123 --password Test123456789 --user-principal-name {upn}').get_output_in_json()
            self.kwargs['object_id'] = result['id']
            try:
                # Test create role assignment with description, condition and condition_version
                self.cmd('role assignment create --assignee-object-id {object_id} --assignee-principal-type User --role reader --scope {rg_id} '
                         # Include double quotes to tell shlex to treat arguments as a whole
                         '--description "{description}" '
                         '--condition "{condition}" --condition-version {condition_version}',
                         checks=[
                             self.check("description", "{description}"),
                             self.check("condition", "{condition}"),
                             self.check("conditionVersion", "{condition_version}")
                         ])
                self.cmd('role assignment delete -g {rg}')
    
                # Test create role assignment with description, condition. condition_version defaults to 2.0
                self.cmd('role assignment create --assignee-object-id {object_id} --assignee-principal-type User --role reader --scope {rg_id} '
                         '--description "{description}" '
                         '--condition "{condition}"',
                         checks=[
                             self.check("description", "{description}"),
                             self.check("condition", "{condition}"),
                             self.check("conditionVersion", "2.0")
                         ])
                self.cmd('role assignment delete -g {rg}')
    
                # Test error is raised if condition-version is set but condition is not
                with self.assertRaisesRegex(CLIError, "--condition must be set"):
                    self.cmd('role assignment create --assignee-object-id {object_id} --assignee-principal-type User --role reader --scope {rg_id} '
                             '--condition-version {condition_version}')
    
                # Update
                output = self.cmd('role assignment create --assignee-object-id {object_id} --assignee-principal-type User --role reader --scope {rg_id} '
                                  '--description "{description}" '
                                  '--condition "{condition}" --condition-version {condition_version}').get_output_in_json()
    
                updated_description = "Some updated description."
                output['description'] = updated_description
                import shlex
                from ..util import escape_apply_kwargs
                # The json contains both single (in description) and double quotes, use shlex to quote and escape it,
                # then escape it again before being passed to self.cmd
                output_json = escape_apply_kwargs(shlex.quote(json.dumps(output)))
    
>               self.cmd("role assignment update --role-assignment {}".format(output_json),
                         checks=[self.check("description", updated_description)])

src/azure-cli/azure/cli/command_modules/role/tests/latest/test_role.py:614: 
 
 
 
                                     
src/azure-cli-testsdk/azure/cli/testsdk/base.py:177: in cmd
    return execute(self.cli_ctx, command, expect_failure=expect_failure).assert_with_checks(checks)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-testsdk/azure/cli/testsdk/base.py:252: in init
    self.in_process_execute(cli_ctx, command, expect_failure=expect_failure)
src/azure-cli-testsdk/azure/cli/testsdk/base.py:315: in in_process_execute
    raise ex.exception
env/lib/python3.13/site-packages/knack/cli.py:233: in invoke
    cmd_result = self.invocation.execute(args)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:677: in execute
    raise ex
src/azure-cli-core/azure/cli/core/commands/init.py:820: in run_jobs_serially
    results.append(self.run_job(expanded_arg, cmd_copy))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:789: in run_job
    result = cmd_copy(params)
             ^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:335: in call
    return self.handler(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/command_operation.py:120: in handler
    return op(**command_args)
           ^^^^^^^^^^^^^^^^^^
src/azure-cli/azure/cli/command_modules/role/custom.py:309: in update_role_assignment
    original_assignment = assignments_client.get(scope, name)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/core/tracing/decorator.py:119: in wrapper_use_tracer
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/mgmt/authorization/operations/operations.py:9836: in get
    request = build_role_assignments_get_request(
env/lib/python3.13/site-packages/azure/mgmt/authorization/operations/operations.py:1762: in build_role_assignments_get_request
    "scope": SERIALIZER.url("scope", scope, "str", skip_quote=True),
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/azure/mgmt/authorization/utils/serialization.py:700: in url
    output = self.serialize_data(data, data_type, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 
 
 
 
 
 
 
 
                               

self = <azure.mgmt.authorization._utils.serialization.Serializer object at 0x7fa95ebd0050>
data = None, data_type = 'str', kwargs = {'skip_quote': True}

    def serialize_data(self, data, data_type, **kwargs):
        """Serialize generic data according to supplied data type.
    
        :param object data: The data to be serialized.
        :param str data_type: The type to be serialized from.
        :raises AttributeError: if required data is None.
        :raises ValueError: if data is None
        :raises SerializationError: if serialization fails.
        :returns: The serialized data.
        :rtype: str, int, float, bool, dict, list
        """
        if data is None:
>           raise ValueError("No value for given attribute")
E           ValueError: No value for given attribute

env/lib/python3.13/site-packages/azure/mgmt/authorization/_utils/serialization.py:777: ValueError
azure/cli/command_modules/role/tests/latest/test_role.py:553
Failed test_role_assignment_create_using_principal_type The error message is too long, please check the pipeline log for details. azure/cli/command_modules/role/tests/latest/test_role.py:459
Failed test_role_assignment_scenario The error message is too long, please check the pipeline log for details. azure/cli/command_modules/role/tests/latest/test_role.py:322
Failed test_role_assignment_handle_conflicted_assignments The error message is too long, please check the pipeline log for details. azure/cli/command_modules/role/tests/latest/test_role.py:765
🔄search
🔄latest
🔄3.12
️✔️3.13
️✔️security
️✔️latest
️✔️3.12
️✔️3.13
🔄servicebus
🔄latest
🔄3.12
️✔️3.13
️✔️serviceconnector
️✔️latest
️✔️3.12
️✔️3.13
️✔️servicefabric
️✔️latest
️✔️3.12
️✔️3.13
🔄signalr
🔄latest
🔄3.12
🔄3.13
🔄sql
🔄latest
🔄3.12
️✔️3.13
🔄sqlvm
🔄latest
🔄3.12
️✔️3.13
🔄storage
🔄latest
🔄3.12
🔄3.13
🔄synapse
🔄latest
🔄3.12
️✔️3.13
️✔️telemetry
️✔️latest
️✔️3.12
️✔️3.13
️✔️util
️✔️latest
️✔️3.12
️✔️3.13
❌vm
❌latest
🔄3.12
❌3.13
Type Test Case Error Message Line
Failed test_vm_explicit_msi self = <azure.cli.core.commands.AzCliCommandInvoker object at 0x7f1a79cea060>
parsed_ns = Namespace(log_verbosity_verbose=False, log_verbosity_debug=False, log_verbosity_only_show_errors=False, output_for...usage=None, description='', formatter_class=<class 'argparse.HelpFormatter'>, conflict_handler='error', add_help=True))

    def validation(self, parsed_ns):
        try:
            cmd_validator = getattr(parsed_ns, 'command_validator', None)
            if cmd_validator:
>               self.validate_cmd_level(parsed_ns, cmd_validator)

env/lib/python3.13/site-packages/knack/invocation.py:111: 
 
 
 
 
 
 
                                  
src/azure-cli-core/azure/cli/core/commands/init.py:1001: in validate_cmd_level
    cmd_validator(**self.build_kwargs(cmd_validator, ns))
src/azure-cli/azure/cli/command_modules/vm/validators.py:1630: in process_vm_create_namespace
    validate_vm_vmss_msi(cmd, namespace)
src/azure-cli/azure/cli/command_modules/vm/validators.py:1406: in validate_vm_vmss_msi
    setattr(namespace, 'identity_role_id', resolve_role_id(cmd.cli_ctx, namespace.identity_role,
src/azure-cli/azure/cli/command_modules/vm/validators.py:1587: in resolve_role_id
    role_defs = list(client.list(scope, "roleName eq '{}'".format(role)))
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 
 
 
 
 
 
 
 
                               

args = (<azure.mgmt.authorization.operations.operations.RoleDefinitionsOperations object at 0x7f1a79b51940>, '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest.rg000001', "roleName eq 'reader'")
kwargs = {}, merge_span = False, passed_in_parent = None, tracing_options = {}
tracing_enabled = False, user_enabled = None

    @functools.wraps(func)
    def wrapper_use_tracer(*args: Any, **kwargs: Any) -> T:
        merge_span = kwargs.pop("merge_span", False)
        passed_in_parent = kwargs.pop("parent_span", None)
    
        # If we are already in the span context of a decorated function, don't trace.
        if in_span_context.get():
            return func(*args, **kwargs)
    
        # This will be popped in the pipeline or transport runner.
        tracing_options: TracingOptions = kwargs.get("tracing_options", {})
        tracing_enabled = settings.tracing_enabled()
    
        # User can explicitly disable tracing for this request.
        user_enabled = tracing_options.get("enabled")
    
        # If tracing is disabled globally and user didn't explicitly enable it, don't trace.
        if user_enabled is False or (not tracing_enabled and user_enabled is None):
>           return func(*args, **kwargs)
                   ^^^^^^^^^^^^^^^^^^^^^
E           TypeError: RoleDefinitionsOperations.list() takes 2 positional arguments but 3 were given

env/lib/python3.13/site-packages/azure/core/tracing/decorator.py:119: TypeError

During handling of the above exception, another exception occurred:

self = <latest.test_vm_commands.MSIScenarioTest testMethod=test_vm_explicit_msi>
resource_group = 'clitest.rg000001'

    @AllowLargeResponse(size_kb=99999)
    @ResourceGroupPreparer(random_name_length=20)
    def test_vm_explicit_msi(self, resource_group):
    
        self.kwargs.update({
            'emsi': 'id1',
            'emsi2': 'id2',
            'vm': 'vm1',
            'sub': self.get_subscription_id(),
            'scope': '/subscriptions/{}/resourceGroups/{}'.format(self.get_subscription_id(), resource_group),
            'user': 'ubuntuadmin',
            'subnet': 'subnet1',
            'vnet': 'vnet1'
        })
    
        # create a managed identity
        emsi_result = self.cmd('identity create -g {rg} -n {emsi} --tags tag1=d1', checks=[
            self.check('name', '{emsi}'),
            self.check('tags.tag1', 'd1')]).get_output_in_json()
        emsi2_result = self.cmd('identity create -g {rg} -n {emsi2}').get_output_in_json()
    
        # create a vm with only user assigned identity
        result = self.cmd(
            'vm create -g {rg} -n vm2 --image Debian11 --assign-identity {emsi} '
            '--generate-ssh-keys --admin-username {user} --subnet {subnet} --vnet-name {vnet} --nsg-rule NONE --size Standard_B2ms', checks=[
            self.check('identity.role', None),
            self.check('identity.scope', None),
        ]).get_output_in_json()
    
        # Disable default outbound access
        self.cmd(
            'network vnet subnet update -g {rg} --vnet-name {vnet} -n {subnet} --default-outbound-access false')
    
        emsis = [x.lower() for x in result['identity']['userAssignedIdentities'].keys()]
        self.assertEqual(emsis, [emsi_result['id'].lower()])
        self.assertFalse(result['identity']['systemAssignedIdentity'])
    
        # create a vm with system + user assigned identities
>       result = self.cmd('vm create -g {rg} -n {vm} --image Debian11 --assign-identity {emsi} [system] --role reader --scope {scope} --generate-ssh-keys --admin-username {user} --subnet {subnet} --vnet-name {vnet} --nsg-rule NONE --size Standard_B2ms').get_output_in_json()
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

src/azure-cli/azure/cli/command_modules/vm/tests/latest/test_vm_commands.py:6402: 
 
                                       
src/azure-cli-testsdk/azure/cli/testsdk/base.py:177: in cmd
    return execute(self.cli_ctx, command, expect_failure=expect_failure).assert_with_checks(checks)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-testsdk/azure/cli/testsdk/base.py:252: in init
    self.in_process_execute(cli_ctx, command, expect_failure=expect_failure)
src/azure-cli-testsdk/azure/cli/testsdk/base.py:303: in in_process_execute
    self.exit_code = cli_ctx.invoke(shlex.split(command), out_file=stdout_buf) or 0
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/knack/cli.py:250: in invoke
    raise ex
env/lib/python3.13/site-packages/knack/cli.py:233: in invoke
    cmd_result = self.invocation.execute(args)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:665: in execute
    self.validation(expanded_arg)
env/lib/python3.13/site-packages/knack/invocation.py:118: in validation
    getattr(parsed_ns, 'parser', self.parser).validation_error(str(err))
src/azure-cli-core/azure/cli/core/parser.py:150: in validation_error
    self.exit(2)
 
 
 
 
                                   _ 

self = AzCliCommandParser(prog='az vm create', usage=None, description='', formatter_class=<class 'argparse.HelpFormatter'>, conflict_handler='error', add_help=True)
status = 2, message = None

    def exit(self, status=0, message=None):
        if message:
            self._print_message(message, _sys.stderr)
>       _sys.exit(status)
E       SystemExit: 2

/opt/hostedtoolcache/Python/3.13.11/x64/lib/python3.13/argparse.py:2645: SystemExit
azure/cli/command_modules/vm/tests/latest/test_vm_commands.py:6363
Failed test_vm_msi self = <azure.cli.core.commands.AzCliCommandInvoker object at 0x7f1a7c035bd0>
parsed_ns = Namespace(log_verbosity_verbose=False, log_verbosity_debug=False, log_verbosity_only_show_errors=False, output_for...usage=None, description='', formatter_class=<class 'argparse.HelpFormatter'>, conflict_handler='error', add_help=True))

    def validation(self, parsed_ns):
        try:
            cmd_validator = getattr(parsed_ns, 'command_validator', None)
            if cmd_validator:
>               self.validate_cmd_level(parsed_ns, cmd_validator)

env/lib/python3.13/site-packages/knack/invocation.py:111: 
 
 
 
 
 
 
                                  
src/azure-cli-core/azure/cli/core/commands/init.py:1001: in validate_cmd_level
    cmd_validator(**self.build_kwargs(cmd_validator, ns))
src/azure-cli/azure/cli/command_modules/vm/validators.py:1630: in process_vm_create_namespace
    validate_vm_vmss_msi(cmd, namespace)
src/azure-cli/azure/cli/command_modules/vm/validators.py:1406: in validate_vm_vmss_msi
    setattr(namespace, 'identity_role_id', resolve_role_id(cmd.cli_ctx, namespace.identity_role,
src/azure-cli/azure/cli/command_modules/vm/validators.py:1587: in resolve_role_id
    role_defs = list(client.list(scope, "roleName eq '{}'".format(role)))
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 
 
 
 
 
 
 
 
                               

args = (<azure.mgmt.authorization.operations.operations.RoleDefinitionsOperations object at 0x7f1a7bf5e3c0>, '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vm_msi000001', "roleName eq 'Contributor'")
kwargs = {}, merge_span = False, passed_in_parent = None, tracing_options = {}
tracing_enabled = False, user_enabled = None

    @functools.wraps(func)
    def wrapper_use_tracer(*args: Any, **kwargs: Any) -> T:
        merge_span = kwargs.pop("merge_span", False)
        passed_in_parent = kwargs.pop("parent_span", None)
    
        # If we are already in the span context of a decorated function, don't trace.
        if in_span_context.get():
            return func(*args, **kwargs)
    
        # This will be popped in the pipeline or transport runner.
        tracing_options: TracingOptions = kwargs.get("tracing_options", {})
        tracing_enabled = settings.tracing_enabled()
    
        # User can explicitly disable tracing for this request.
        user_enabled = tracing_options.get("enabled")
    
        # If tracing is disabled globally and user didn't explicitly enable it, don't trace.
        if user_enabled is False or (not tracing_enabled and user_enabled is None):
>           return func(*args, **kwargs)
                   ^^^^^^^^^^^^^^^^^^^^^
E           TypeError: RoleDefinitionsOperations.list() takes 2 positional arguments but 3 were given

env/lib/python3.13/site-packages/azure/core/tracing/decorator.py:119: TypeError

During handling of the above exception, another exception occurred:

self = <latest.test_vm_commands.MSIScenarioTest testMethod=test_vm_msi>
resource_group = 'cli_test_vm_msi000001'

    @AllowLargeResponse(size_kb=99999)
    @ResourceGroupPreparer(name_prefix='cli_test_vm_msi')
    def test_vm_msi(self, resource_group):
        subscription_id = self.get_subscription_id()
    
        self.kwargs.update({
            'scope': '/subscriptions/{}/resourceGroups/{}'.format(subscription_id, resource_group),
            'vm1': 'vm1',
            'vm1_id': '/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Compute/virtualMachines/vm1'.format(subscription_id, resource_group),
            'vm2': 'vm2',
            'vm3': 'vm3',
            'sub': subscription_id,
            'subnet': 'subnet1',
            'vnet': 'vnet1'
        })
    
        with self.assertRaisesRegex(ArgumentUsageError, "please specify both --role and --scope"):
            self.cmd('vm create -g {rg} -n {vm1} --image Debian:debian-10:10:latest --assign-identity --admin-username admin123 '
                     '--admin-password PasswordPassword1! --subnet {subnet} --vnet-name {vnet} --nsg-rule NONE --scope {scope}')
    
        with self.assertRaisesRegex(ArgumentUsageError, "please specify both --role and --scope"):
            self.cmd('vm create -g {rg} -n {vm1} --image Debian:debian-10:10:latest --assign-identity --admin-username admin123 '
                     '--admin-password PasswordPassword1! --subnet {subnet} --vnet-name {vnet} --nsg-rule NONE --role Contributor')
    
        with mock.patch('azure.cli.core.commands.arm.gen_guid', side_effect=self.create_guid):
            # create a linux vm with default configuration
>           self.cmd('vm create -g {rg} -n {vm1} --image Debian:debian-10:10:latest --assign-identity --admin-username admin123 '
                     '--admin-password PasswordPassword1! --scope {scope} --role Contributor --subnet {subnet} --vnet-name {vnet} --nsg-rule NONE', checks=[
                self.check('identity.role', 'Contributor'),
                self.check('identity.scope', '/subscriptions/{sub}/resourceGroups/{rg}'),
            ])

src/azure-cli/azure/cli/command_modules/vm/tests/latest/test_vm_commands.py:6221: 
 
 
                                      
src/azure-cli-testsdk/azure/cli/testsdk/base.py:177: in cmd
    return execute(self.cli_ctx, command, expect_failure=expect_failure).assert_with_checks(checks)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-testsdk/azure/cli/testsdk/base.py:252: in init
    self.in_process_execute(cli_ctx, command, expect_failure=expect_failure)
src/azure-cli-testsdk/azure/cli/testsdk/base.py:303: in in_process_execute
    self.exit_code = cli_ctx.invoke(shlex.split(command), out_file=stdout_buf) or 0
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/knack/cli.py:250: in invoke
    raise ex
env/lib/python3.13/site-packages/knack/cli.py:233: in invoke
    cmd_result = self.invocation.execute(args)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:665: in execute
    self.validation(expanded_arg)
env/lib/python3.13/site-packages/knack/invocation.py:118: in validation
    getattr(parsed_ns, 'parser', self.parser).validation_error(str(err))
src/azure-cli-core/azure/cli/core/parser.py:150: in validation_error
    self.exit(2)
 
 
 
 
 
                                   

self = AzCliCommandParser(prog='az vm create', usage=None, description='', formatter_class=<class 'argparse.HelpFormatter'>, conflict_handler='error', add_help=True)
status = 2, message = None

    def exit(self, status=0, message=None):
        if message:
            self._print_message(message, _sys.stderr)
>       _sys.exit(status)
E       SystemExit: 2

/opt/hostedtoolcache/Python/3.13.11/x64/lib/python3.13/argparse.py:2645: SystemExit
azure/cli/command_modules/vm/tests/latest/test_vm_commands.py:6194
Failed test_vmss_explicit_msi self = <azure.cli.core.commands.AzCliCommandInvoker object at 0x7f1a7c9b7890>
parsed_ns = Namespace(log_verbosity_verbose=False, log_verbosity_debug=False, log_verbosity_only_show_errors=False, output_for...usage=None, description='', formatter_class=<class 'argparse.HelpFormatter'>, conflict_handler='error', add_help=True))

    def validation(self, parsed_ns):
        try:
            cmd_validator = getattr(parsed_ns, 'command_validator', None)
            if cmd_validator:
>               self.validate_cmd_level(parsed_ns, cmd_validator)

env/lib/python3.13/site-packages/knack/invocation.py:111: 
 
 
 
 
 
 
                                  
src/azure-cli-core/azure/cli/core/commands/init.py:1001: in validate_cmd_level
    cmd_validator(**self.build_kwargs(cmd_validator, ns))
src/azure-cli/azure/cli/command_modules/vm/validators.py:1934: in process_vmss_create_namespace
    validate_vm_vmss_msi(cmd, namespace)
src/azure-cli/azure/cli/command_modules/vm/validators.py:1406: in validate_vm_vmss_msi
    setattr(namespace, 'identity_role_id', resolve_role_id(cmd.cli_ctx, namespace.identity_role,
src/azure-cli/azure/cli/command_modules/vm/validators.py:1587: in resolve_role_id
    role_defs = list(client.list(scope, "roleName eq '{}'".format(role)))
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 
 
 
 
 
 
 
 
                               

args = (<azure.mgmt.authorization.operations.operations.RoleDefinitionsOperations object at 0x7f1a7be81550>, '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vmss_explicit_msi000001', "roleName eq 'reader'")
kwargs = {}, merge_span = False, passed_in_parent = None, tracing_options = {}
tracing_enabled = False, user_enabled = None

    @functools.wraps(func)
    def wrapper_use_tracer(*args: Any, **kwargs: Any) -> T:
        merge_span = kwargs.pop("merge_span", False)
        passed_in_parent = kwargs.pop("parent_span", None)
    
        # If we are already in the span context of a decorated function, don't trace.
        if in_span_context.get():
            return func(*args, **kwargs)
    
        # This will be popped in the pipeline or transport runner.
        tracing_options: TracingOptions = kwargs.get("tracing_options", {})
        tracing_enabled = settings.tracing_enabled()
    
        # User can explicitly disable tracing for this request.
        user_enabled = tracing_options.get("enabled")
    
        # If tracing is disabled globally and user didn't explicitly enable it, don't trace.
        if user_enabled is False or (not tracing_enabled and user_enabled is None):
>           return func(*args, **kwargs)
                   ^^^^^^^^^^^^^^^^^^^^^
E           TypeError: RoleDefinitionsOperations.list() takes 2 positional arguments but 3 were given

env/lib/python3.13/site-packages/azure/core/tracing/decorator.py:119: TypeError

During handling of the above exception, another exception occurred:

self = <latest.test_vm_commands.MSIScenarioTest testMethod=test_vmss_explicit_msi>
resource_group = 'cli_test_vmss_explicit_msi000001'

    @ResourceGroupPreparer(name_prefix='cli_test_vmss_explicit_msi', location='westcentralus')
    def test_vmss_explicit_msi(self, resource_group):
    
        self.kwargs.update({
            'emsi': 'id1',
            'emsi2': 'id2',
            'vmss': 'vmss1',
            'sub': self.get_subscription_id(),
            'scope': '/subscriptions/{}/resourceGroups/{}'.format(self.get_subscription_id(), resource_group)
        })
    
        # create a managed identity
        emsi_result = self.cmd('identity create -g {rg} -n {emsi}').get_output_in_json()
        emsi2_result = self.cmd('identity create -g {rg} -n {emsi2}').get_output_in_json()
    
        # create a vmss with system + user assigned identities
>       result = self.cmd('vmss create -g {rg} -n {vmss} --image Canonical:UbuntuServer:16.04-LTS:latest '
                          '--assign-identity {emsi} [system] --role reader --scope {scope} --instance-count 1 '
                          '--generate-ssh-keys --admin-username ubuntuadmin --orchestration-mode Uniform '
                          '--lb-sku Standard --vm-sku Standard_D2s_v3').get_output_in_json()

src/azure-cli/azure/cli/command_modules/vm/tests/latest/test_vm_commands.py:6452: 
 
                                       
src/azure-cli-testsdk/azure/cli/testsdk/base.py:177: in cmd
    return execute(self.cli_ctx, command, expect_failure=expect_failure).assert_with_checks(checks)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-testsdk/azure/cli/testsdk/base.py:252: in init
    self.in_process_execute(cli_ctx, command, expect_failure=expect_failure)
src/azure-cli-testsdk/azure/cli/testsdk/base.py:303: in in_process_execute
    self.exit_code = cli_ctx.invoke(shlex.split(command), out_file=stdout_buf) or 0
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.13/site-packages/knack/cli.py:250: in invoke
    raise ex
env/lib/python3.13/site-packages/knack/cli.py:233: in invoke
    cmd_result = self.invocation.execute(args)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:665: in execute
    self.validation(expanded_arg)
env/lib/python3.13/site-packages/knack/invocation.py:118: in validation
    getattr(parsed_ns, 'parser', self.parser).validation_error(str(err))
src/azure-cli-core/azure/cli/core/parser.py:150: in validation_error
    self.exit(2)
 
 
 
 
                                   _ 

self = AzCliCommandParser(prog='az vmss create', usage=None, description='', formatter_class=<class 'argparse.HelpFormatter'>, conflict_handler='error', add_help=True)
status = 2, message = None

    def exit(self, status=0, message=None):
        if message:
            self._print_message(message, _sys.stderr)
>       _sys.exit(status)
E       SystemExit: 2

/opt/hostedtoolcache/Python/3.13.11/x64/lib/python3.13/argparse.py:2645: SystemExit
azure/cli/command_modules/vm/tests/latest/test_vm_commands.py:6435
Failed test_vmss_msi The error message is too long, please check the pipeline log for details. azure/cli/command_modules/vm/tests/latest/test_vm_commands.py:6256

@azure-client-tools-bot-prd
Copy link
Copy Markdown

azure-client-tools-bot-prd Bot commented Mar 31, 2026

⚠️AzureCLI-BreakingChangeTest
⚠️role
rule cmd_name rule_message suggest_message
⚠️ 1011 - SubgroupAdd role deny-assignment sub group role deny-assignment added

@yonzhan
Copy link
Copy Markdown
Collaborator

yonzhan commented Mar 31, 2026

Thank you for your contribution! We will review the pull request and get back to you soon.

@github-actions
Copy link
Copy Markdown

The git hooks are available for azure-cli and azure-cli-extensions repos. They could help you run required checks before creating the PR.

Please sync the latest code with latest dev branch (for azure-cli) or main branch (for azure-cli-extensions).
After that please run the following commands to enable git hooks:

pip install azdev --upgrade
azdev setup -c <your azure-cli repo path> -r <your azure-cli-extensions repo path>

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds first-class az role deny-assignment CRUD support (focused on PP1 user-assigned deny assignments) to align with existing role assignment workflows, including command registration, parameters, help, and tests.

Changes:

  • Register az role deny-assignment list/show/create/delete commands and add a table transformer for list output.
  • Implement deny-assignment list/show/create/delete custom handlers with PP1 validation.
  • Add help/params, linter exclusions for long options, and introduce a new deny-assignment test file.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/azure-cli/azure/cli/command_modules/role/custom.py Adds deny-assignment list/show/create/delete implementations and PP1 input validation.
src/azure-cli/azure/cli/command_modules/role/commands.py Registers new role deny-assignment commands and list table transformer.
src/azure-cli/azure/cli/command_modules/role/_params.py Defines CLI parameters for deny-assignment commands.
src/azure-cli/azure/cli/command_modules/role/_help.py Adds help text and examples for the new/updated deny-assignment commands.
src/azure-cli/azure/cli/command_modules/role/linter_exclusions.yml Excludes option-length lint rules for new long parameter names.
src/azure-cli/azure/cli/command_modules/role/tests/latest/test_deny_assignment.py Adds scenario/live tests covering list/show and PP1 create/delete validation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +627 to +638
'deny_assignment_name': deny_assignment_name,
'description': description or '',
'permissions': [{
'actions': actions or [],
'not_actions': not_actions or [],
'data_actions': [],
'not_data_actions': []
}],
'scope': scope,
'principals': principals,
'exclude_principals': exclude_principals,
'is_system_protected': False
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create_deny_assignment builds the request body as a plain dict using snake_case keys (e.g. deny_assignment_name, not_actions, exclude_principals). For mgmt SDK operations, dicts are typically serialized as-is, so the service will receive incorrect field names (it expects camelCase JSON, or a proper SDK model instance). Use the azure-mgmt-authorization model types via get_sdk(..., mod='models') (similar to RoleApiHelper.create_role_assignment) or ensure the payload keys match the service JSON contract exactly.

Suggested change
'deny_assignment_name': deny_assignment_name,
'description': description or '',
'permissions': [{
'actions': actions or [],
'not_actions': not_actions or [],
'data_actions': [],
'not_data_actions': []
}],
'scope': scope,
'principals': principals,
'exclude_principals': exclude_principals,
'is_system_protected': False
'denyAssignmentName': deny_assignment_name,
'description': description or '',
'permissions': [{
'actions': actions or [],
'notActions': not_actions or [],
'dataActions': [],
'notDataActions': []
}],
'scope': scope,
'principals': principals,
'excludePrincipals': exclude_principals,
'isSystemProtected': False

Copilot uses AI. Check for mistakes.
Comment on lines +641 to +642
return deny_client.create(scope=scope, deny_assignment_id=assignment_name,
parameters=deny_assignment_params)
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This calls authorization_client.deny_assignments.create(...), but the repo currently pins azure-mgmt-authorization==5.0.0b1 (which does not include denyAssignments create/delete per the PR description). Without bumping the SDK dependency (or adding a fallback implementation / friendly error), this command will raise AttributeError at runtime.

Copilot uses AI. Check for mistakes.
Comment on lines +645 to +654
def delete_deny_assignment(cmd, scope=None, deny_assignment_id=None, deny_assignment_name=None):
"""Delete a user-assigned deny assignment."""
authorization_client = _auth_client_factory(cmd.cli_ctx, scope)
deny_client = authorization_client.deny_assignments

if deny_assignment_id:
return deny_client.delete_by_id(deny_assignment_id)
if deny_assignment_name and scope:
return deny_client.delete(scope=scope, deny_assignment_id=deny_assignment_name)
raise CLIError('Please provide --id, or both --name and --scope.')
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same dependency issue as create: deny_client.delete(...)/delete_by_id(...) will fail at runtime unless the pinned azure-mgmt-authorization version includes these methods. Consider either updating the dependency in this PR or detecting missing methods and raising a clear CLIError instructing users to upgrade.

Copilot uses AI. Check for mistakes.
Comment on lines +397 to +398
c.argument('deny_assignment_name', options_list=['--name', '-n'],
help='The display name of the deny assignment.')
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deny_assignment_name is defined at the role deny-assignment group level, which makes --name/-n show up for subcommands like list even though list_deny_assignments doesn't accept that parameter. If a user supplies --name on list, the handler will receive an unexpected kwarg and fail. Recommend removing deny_assignment_name from the group context and defining --name only on show/create/delete where it is supported.

Suggested change
c.argument('deny_assignment_name', options_list=['--name', '-n'],
help='The display name of the deny assignment.')

Copilot uses AI. Check for mistakes.
c.argument('exclude_principal_ids', nargs='+', options_list=['--exclude-principal-ids'],
help='Space-separated list of principal object IDs to exclude from the deny. '
'At least one is required for user-assigned deny assignments.')
c.argument('exclude_principal_types', nargs='+', options_list=['--exclude-principal-types'],
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--exclude-principal-types is documented as having accepted values, but the argument doesn't enforce them. To keep validation consistent with role assignment create --assignee-principal-type, use arg_type=get_enum_type([...]) (or an Enum) so invalid values are caught client-side with a clear error.

Suggested change
c.argument('exclude_principal_types', nargs='+', options_list=['--exclude-principal-types'],
c.argument('exclude_principal_types', nargs='+', options_list=['--exclude-principal-types'],
arg_type=get_enum_type(['User', 'Group', 'ServicePrincipal']),

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +35
class DenyAssignmentListTest(ScenarioTest):
"""Tests for az role deny-assignment list — works on any subscription."""

def test_deny_assignment_list(self):
"""List deny assignments at the subscription scope."""
result = self.cmd('role deny-assignment list').get_output_in_json()
# Result should be a list (may be empty if no deny assignments exist)
self.assertIsInstance(result, list)

def test_deny_assignment_list_with_scope(self):
"""List deny assignments at a specific scope."""
self.cmd('role deny-assignment list --scope /subscriptions/{sub}',
checks=[self.check('type(@)', 'array')])

def test_deny_assignment_list_with_filter(self):
"""List deny assignments with OData filter."""
result = self.cmd(
'role deny-assignment list --filter "atScope()"'
).get_output_in_json()
self.assertIsInstance(result, list)

Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file adds ScenarioTest cases for role deny-assignment list that will require a new VCR recording to pass in playback mode, but no corresponding recording YAML is added under tests/latest/recordings. Either add recordings for these scenario tests or convert them to mock-based tests (or LiveScenarioTest if they must be live-only) so CI doesn't fail.

Copilot uses AI. Check for mistakes.
Comment on lines +56 to +61
self.kwargs.update({
'scope': '/subscriptions/{sub}',
'name': 'CLI Test Deny Assignment',
'action': 'Microsoft.Authorization/roleAssignments/write',
# Use a well-known object ID for exclusion (replace with a real SP in your test env)
'exclude_id': self.create_guid()
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exclude_id is set to self.create_guid(), which is a random GUID and is unlikely to correspond to a real principal in the tenant. If the denyAssignment API validates excluded principals, this live test will fail reliably. Prefer creating a real principal in the test setup (or using the signed-in user/service principal object id) and passing its object ID here.

Suggested change
self.kwargs.update({
'scope': '/subscriptions/{sub}',
'name': 'CLI Test Deny Assignment',
'action': 'Microsoft.Authorization/roleAssignments/write',
# Use a well-known object ID for exclusion (replace with a real SP in your test env)
'exclude_id': self.create_guid()
signed_in_user = self.cmd('ad signed-in-user show').get_output_in_json()
exclude_id = signed_in_user['id']
self.kwargs.update({
'scope': '/subscriptions/{sub}',
'name': 'CLI Test Deny Assignment',
'action': 'Microsoft.Authorization/roleAssignments/write',
'exclude_id': exclude_id

Copilot uses AI. Check for mistakes.
# These tests require a subscription with the UserAssignedDenyAssignment feature flag enabled.

import unittest
from unittest import mock
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import: from unittest import mock is not used in this test file. Please remove it to keep linting clean.

Suggested change
from unittest import mock

Copilot uses AI. Check for mistakes.
Update create command to support two modes:
- Everyone mode (default): denies all principals, requires exclude-principal-ids
- Per-principal mode: denies a specific User or ServicePrincipal via --principal-id/--principal-type

API changes from DA PR msazure/One#15293894:
- 3P UADA can now target specific User and ServicePrincipal principals
- Group type principals are explicitly disallowed
- Single-principal-per-UADA constraint enforced

Changes:
- custom.py: Add principal_id/principal_type params, dual-mode logic, Group rejection
- _params.py: Add --principal-id and --principal-type (enum) arguments
- _help.py: Update long-summary and examples for both modes
- tests: Add per-principal CRUD, Group rejection, missing-param validation tests

Co-authored-by: Copilot <[email protected]>
Now that azure-mgmt-authorization 5.0.0b2 has been published to PyPI
(2026-05-07), this picks up the new DenyAssignment management plane
operations (BeginCreateOrUpdate / BeginDelete) added in PR #46223.

This unblocks the deny-assignment CLI commands in this PR from
running against the new SDK surface.
@jruttle jruttle requested a review from necusjz as a code owner May 7, 2026 12:55
@jruttle
Copy link
Copy Markdown
Member Author

jruttle commented May 7, 2026

Bumped azure-mgmt-authorization from 5.0.0b15.0.0b2 in src/azure-cli/setup.py (commit 20fe3a79).

This picks up the new DenyAssignment management-plane operations (BeginCreateOrUpdate / BeginDelete) that shipped in azure-mgmt-authorization 5.0.0b2 on PyPI today (release request Azure/azure-sdk-for-python#46551, released by @msyyc at 10:30 UTC).

This was the last upstream blocker on this PR — the CLI commands here now have a real published SDK surface to call against. CC for review awareness: prior reviewers / CLI mgmt module owners.

jruttle added 3 commits May 7, 2026 14:20
Follow-up to commit 20fe3a7 which only bumped setup.py.
The Linux/Darwin/Windows requirements lock files also pinned 5.0.0b1
and caused azdev-linter / azdev-style CI to fail with:

  ERROR: Cannot install azure-cli==2.85.0 and azure-mgmt-authorization==5.0.0b1
  because these package versions have conflicting dependencies.

This commit aligns all three platform lock files with setup.py at 5.0.0b2.
@jruttle
Copy link
Copy Markdown
Member Author

jruttle commented May 7, 2026

Follow-up to commit 20fe3a79 — the setup.py bump alone wasn't enough; the CI errors

`ERROR: Cannot install azure-cli==2.85.0 and azure-mgmt-authorization==5.0.0b1 because these package versions have conflicting dependencies.`
`  The user requested azure-mgmt-authorization==5.0.0b1`
`  azure-cli 2.85.0 depends on azure-mgmt-authorization==5.0.0b2`

…showed the platform lock files (requirements.py3.Linux.txt, Darwin.txt, windows.txt) were still pinning the old beta. Now bumped in three follow-up commits:

CI should now re-run cleanly on the new HEAD.

@jruttle
Copy link
Copy Markdown
Member Author

jruttle commented May 7, 2026

/azp run

@azure-pipelines
Copy link
Copy Markdown

Commenter does not have sufficient privileges for PR 33109 in repo Azure/azure-cli

@jruttle
Copy link
Copy Markdown
Member Author

jruttle commented May 7, 2026

Hi @yonzhan @isra-fel — could one of you (or anyone with write access) re-trigger the Azure DevOps validation pipelines on this PR?

The two required checks Azure.azure-cli and Azure.azure-cli Full Test are stuck on "Expected — Waiting for status to be reported". They successfully ran when the PR was first opened on 31 March (per the azure-client-tools-bot-prd "Validation for Azure CLI Full Test Starting..." comments), but those statuses are stale because today's four follow-up commits (SDK version bump from 5.0.0b15.0.0b2 in setup.py + the three platform lock files) advanced HEAD to 5c4caa29, and the AzDO pipelines don't auto-re-trigger on subsequent pushes from a fork.

Either of these comments should kick them off:

  • /azp run
  • @azure-client-tools-bot-prd run

Background context: the SDK dependency bump unblocks this PR — azure-mgmt-authorization 5.0.0b2 was published to PyPI earlier today (2026-05-07 10:30 UTC) and contains the new DenyAssignment management-plane operations this PR consumes. All GitHub-side checks (azdev-linter, azdev-style, license/cla) are green on the new HEAD.

Thanks!

@yonzhan
Copy link
Copy Markdown
Collaborator

yonzhan commented May 7, 2026

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 3 pipeline(s).

…ract changes

The new TypeSpec-generated 5.0.0b2 SDK introduced breaking changes that
block the Full Test pipeline. Changes:

* role/custom.py _resolve_role_id: pass `filter` as a keyword argument to
  RoleDefinitionsOperations.list (its signature is now
  `list(scope, *, filter=None)`). This single line was the root cause for
  the wide blast radius across vm/iot/aro/ams/acr/resource modules and the
  role module's own RoleAssignmentScenarioTest cases - all of them resolve
  roles by name and therefore go through this function.

* role/custom.py update_role_assignment: replace
  `RoleAssignment.from_dict(d)` with `RoleAssignment(d)`. The new Model
  base class accepts a JSON mapping directly and no longer exposes
  from_dict.

* role/custom.py create_deny_assignment: switch from passing a snake_case
  dict to constructing SDK model objects (DenyAssignment,
  DenyAssignmentProperties, DenyAssignmentPermission,
  DenyAssignmentPrincipal). The TypeSpec serializer would have written the
  raw snake_case keys to the wire instead of camelCase. Also rename
  `deny_client.create(...)` to `create_or_update(...)` (the method was
  renamed in the new SDK).

* role/custom.py delete_deny_assignment: the new SDK does not expose
  delete_by_id for deny assignments, so parse the resource ID via a new
  _parse_deny_assignment_id helper (case-insensitive regex that handles
  subscription, resource-group, and management-group scopes) and call
  delete(scope, deny_assignment_id).

* role/custom.py show_deny_assignment / delete_deny_assignment /
  create_deny_assignment: move argument validation ahead of the
  _auth_client_factory(...) call so validation-only error paths don't
  require an authenticated session.

* tests/latest/test_deny_assignment.py: convert DenyAssignmentListTest to
  LiveScenarioTest (these hit the live API and have no recorded cassettes
  in the repo), and replace DenyAssignmentShowTest with a unittest-based
  DenyAssignmentShowValidationTest that calls show_deny_assignment()
  directly and asserts the CLIError - both changes remove the dependency
  on a logged-in session in playback CI.

Co-authored-by: Copilot <[email protected]>
@jruttle jruttle changed the title Add az role deny-assignment create/delete commands (user-assigned deny assignments) [Role] Feature: Add az role deny-assignment create/delete commands May 8, 2026
@jruttle
Copy link
Copy Markdown
Member Author

jruttle commented May 8, 2026

Pushed fix commit 2d2c80a0bb addressing the Full Test failures from the 5.0.0b2 SDK bump:

  1. role/custom.py _resolve_role_id ΓÇö RoleDefinitionsOperations.list is now list(scope, *, filter=None) in the new TypeSpec-generated SDK, so filter must be passed as a keyword argument. This was the root cause behind the wide blast radius across vm / iot / aro / ams / acr / resource test modules and the role module's own RoleAssignmentScenarioTest cases ΓÇö all of them resolve roles by name and therefore go through _resolve_role_id.
  2. role/custom.py update_role_assignment ΓÇö replaced RoleAssignment.from_dict(d) with RoleAssignment(d); the new TypeSpec Model base accepts a JSON mapping directly via __init__ and no longer exposes from_dict.
  3. role/custom.py create_deny_assignment — switched from passing a snake_case dict to building SDK model objects (DenyAssignment / DenyAssignmentProperties / DenyAssignmentPermission / DenyAssignmentPrincipal). The TypeSpec serializer would otherwise have written the raw snake_case keys to the wire instead of camelCase. Also renamed deny_client.create(...) → create_or_update(...) (the method was renamed in the new SDK).
  4. role/custom.py delete_deny_assignment ΓÇö the new SDK does not expose delete_by_id for deny assignments, so the --id path now parses the resource ID via a new _parse_deny_assignment_id helper (case-insensitive regex that handles subscription, resource-group, and management-group scopes) and calls delete(scope, deny_assignment_id).
  5. role/custom.py show_deny_assignment / delete_deny_assignment / create_deny_assignment ΓÇö moved argument validation ahead of the _auth_client_factory(...) call so validation-only error paths don't require an authenticated session.
  6. tests/latest/test_deny_assignment.py ΓÇö converted DenyAssignmentListTest to LiveScenarioTest (these hit the live API and there are no recorded cassettes in the repo), and replaced DenyAssignmentShowTest with a unittest.TestCase DenyAssignmentShowValidationTest that calls show_deny_assignment() directly and asserts the CLIError ΓÇö both changes remove the dependency on a logged-in session in playback CI.

Also updated the PR title to [Role] Feature: Add �z role deny-assignment create/delete commands to satisfy the title-format check, and refreshed the Dependency block in the description so it reflects the actual pinned 5.0.0b2 SDK.

/azp run

Extract _build_deny_assignment_model helper to keep the local count under
the project's max-locals=25 threshold (was 26/25 in azdev-style after the
SDK-model refactor).

Co-authored-by: Copilot <[email protected]>
@isra-fel
Copy link
Copy Markdown
Member

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 3 pipeline(s).

Pipeline build 314889 (re-triggered 11 May) still fails because the 8 May patch
did not address every breaking change in the new TypeSpec-generated SDK. This
commit fixes the four remaining classes of issue.

* role/custom.py update_role_assignment: `RoleAssignment(some_dict)` silently
  produces a model with `properties=None` (the new model wraps domain
  attributes under a nested `properties` field, with `__flattened_items`
  exposing them via descriptors only when `properties` is set). The old code
  then read `assignment.scope` -> None and passed scope=None to the SDK URL
  serializer, which raised `ValueError('No value for given attribute')`.
  Read scope/name/principalType/etc. directly from the user-supplied flat
  camelCase dict and send a `{"properties": {...}}` JSON body to .create()
  via its JSON overload - simpler and avoids the new model's flatten/unflatten
  gymnastics. Caused test_role_assignment_create_update.

* role/custom.py list_role_assignments + list_deny_assignments +
  show_deny_assignment: `knack.util.todict` walks `__dict__` and
  therefore returns `{}` for the new MutableMapping-based models, so the
  subsequent `ra['roleDefinitionId']` / `ra['principalId']` lookups
  raised `KeyError`. Added explicit _role_assignment_to_dict and
  _deny_assignment_to_dict adapters that project the model back to the
  legacy flat camelCase shape (with enum -> str coercion) and routed all
  list/show paths through them. Caused
  test_create_for_rbac_password_with_assignment.

* vm/_validators.py, ams/operations/sp.py, containerapp/_utils.py,
  acs/_roleassignments.py: `RoleDefinitionsOperations.list` is now
  `list(scope, *, filter=None)`, so each remaining caller that passed
  filter positionally raised
  `TypeError: list() takes 2 positional arguments but 3 were given`.
  This is the same bug fixed in 2d2c80a for role/custom.py::_resolve_role_id;
  these four call sites were missed. Caused test_vm_msi, test_vm_explicit_msi,
  test_vmss_msi, test_vmss_explicit_msi, test_ams_sp_create_reset, and the
  ACS / Container Apps role-resolution paths.

Co-authored-by: Copilot <[email protected]>
Jonathan Ruttle and others added 2 commits May 12, 2026 10:26
While running the previously-failing tests locally to verify 005aa58,
test_identity_hub continued to fail because azure-cli-core itself has its
own resolve_role_id helper at src/azure-cli-core/azure/cli/core/commands/arm.py
that also passed filter positionally to RoleDefinitionsOperations.list.
This is the shared utility many modules call via core.commands.arm.create_role_assignment.

Same one-line fix as the four module-level callers patched in 005aa58:
pass filter as a keyword argument so it works with the new TypeSpec-generated
SDK signature `list(scope, *, filter=None)`.

Local verification (azure-mgmt-authorization 5.0.0b2 installed):
  * test_role_assignment_create_update PASSED (was: ValueError)
  * test_role_assignment_scenario PASSED
  * test_role_assignment_create_using_principal_type PASSED
  * test_role_assignment_handle_conflicted_assignments PASSED
  * test_create_for_rbac_password_with_assignment PASSED (was: KeyError)
  * test_vm_msi PASSED (was: TypeError)
  * test_vm_explicit_msi PASSED
  * test_vmss_msi PASSED
  * test_vmss_explicit_msi PASSED
  * test_identity_hub PASSED (was: TypeError - this commit's fix)
  * Full role test sweep: 17 passed, 2 LiveOnly skipped
  * Role unit tests: 14 passed

test_ams_sp_create_reset still fails with VCR cassette mismatch (the cassette
was recorded against azure-mgmt-authorization 4.x but dev branch bumped to
5.0.0b1 in Azure#31859 without re-recording AMS cassettes; multiple identical
requests now exhaust the single recorded response). This is pre-existing on
dev and outside the scope of this PR.

Co-authored-by: Copilot <[email protected]>
The azdev-style GitHub Actions check (run id 25725694563) failed flake8 on
the new `_coerce` function added in 005aa58 because it followed the
section comment block with only one blank line. Adding the second blank line
resolves the two reported E302 errors at line 46.

Verified locally with `azdev style --pep8` -> Flake8: PASSED.

Co-authored-by: Copilot <[email protected]>
@jruttle
Copy link
Copy Markdown
Member Author

jruttle commented May 12, 2026

Hi @yonzhan @isra-fel — could one of you /azp run against the new tip when you get a chance? The pipelines last ran on 11 May 07:03 against f498063b72; since then I've pushed three fix commits on top of the previous SDK-contract patch:

Commit Fix
005aa582ab Three more SDK 5.0.0b2 contract breaks (update_role_assignment ValueError, list_role_assignments KeyError, three positional-filter callers in vm/_validators.py, ams/operations/sp.py, containerapp/_utils.py, acs/_roleassignments.py)
a78903e53e One more positional-filter caller in azure-cli-core/azure/cli/core/commands/arm.py::resolve_role_id (the shared utility most modules call)
90642428ce flake8 E302

I ran each of the previously-failing tests locally in playback against azure-mgmt-authorization 5.0.0b2 via azdev and they all pass:

  • test_role_assignment_create_update ✅ (was: ValueError('No value for given attribute'))
  • test_role_assignment_scenario
  • test_role_assignment_create_using_principal_type
  • test_role_assignment_handle_conflicted_assignments
  • test_create_for_rbac_password_with_assignment ✅ (was: KeyError('roleDefinitionId'))
  • test_vm_msi / test_vm_explicit_msi ✅ (was: TypeError)
  • test_vmss_msi / test_vmss_explicit_msi
  • test_identity_hub ✅ (was: TypeError — needed the arm.py fix in a78903e53e)
  • Full role test sweep: 17 passed, 2 LiveOnly skipped
  • Role unit tests: 14 passed
  • azdev style --pep8: PASSED

One pre-existing test, test_ams_sp_create_reset, still fails locally with a VCR cassette mismatch (CannotOverwriteExistingCassetteException, "Matchers failed: [empty]" = cassette exhausted). The AMS cassette was last updated in the 2.85.0 release (692b407c37) and was not re-recorded when the SDK was bumped to 5.0.0b1 in #31859 — the new SDK makes more identical requests than the cassette has responses for. This is pre-existing on dev and outside the scope of this PR; happy to file a separate issue if helpful.

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants